Branch data Line data Source code
1 : : #include "mem.h"
2 : : #include "mem_internal.h"
3 : : #include <string.h>
4 : :
5 : : /**
6 : : * @brief Round a byte size up to the slab-style block size used by libmem.
7 : : *
8 : : * Rounds @p requested_bytes up to the next multiple of @ref MEMORY_BLOCK_BYTES, preserving zero
9 : : * and guarding against overflow. Returns non-zero on error so callers can log and fail early.
10 : : *
11 : : * @param requested_bytes Number of bytes requested by the caller.
12 : : * @param slab_size Output pointer receiving the rounded-up slab size when successful.
13 : : * @return 0 on success; non-zero if @p slab_size is NULL or an overflow occurred.
14 : : */
15 : 72852 : static int round_up_to_block_size(
16 : : size_t requested_bytes,
17 : : size_t *slab_size)
18 : : {
19 [ - + ]: 72852 : if(slab_size == NULL)
20 : : {
21 : 0 : return 1;
22 : : }
23 : :
24 [ - + ]: 72852 : if(requested_bytes == 0)
25 : : {
26 : 0 : *slab_size = 0;
27 : 0 : return 0;
28 : : }
29 : :
30 : 72852 : const size_t remainder = requested_bytes % MEMORY_BLOCK_BYTES;
31 : :
32 [ + + ]: 72852 : if(remainder == 0)
33 : : {
34 : 4 : *slab_size = requested_bytes;
35 : 4 : return 0;
36 : : }
37 : :
38 : 72848 : const size_t padding = MEMORY_BLOCK_BYTES - remainder;
39 : :
40 [ - + ]: 72848 : if(requested_bytes > SIZE_MAX - padding)
41 : : {
42 : 0 : return 1;
43 : : }
44 : :
45 : 72848 : *slab_size = requested_bytes + padding;
46 : 72848 : return 0;
47 : : }
48 : :
49 : : /**
50 : : * @brief Resize a descriptor in logical elements while preserving its current mode
51 : : *
52 : : * Use this helper when a descriptor needs more or less logical room. The new
53 : : * size is measured in elements, not in bytes, so `m_resize(points,10)` means
54 : : * ten `point` objects, while `m_resize(text,10)` means ten characters or code
55 : : * units
56 : : *
57 : : * In data mode the helper treats the descriptor as plain raw storage. In that
58 : : * mode `string_length` must already be `0`, and every successful resize writes
59 : : * `0` there again so raw buffers keep clean non-string metadata. In string
60 : : * mode the helper preserves string mode and never invents new visible content
61 : : * when capacity grows. After a successful resize the element at index
62 : : * @ref memory::string_length is guaranteed to be a zero-valued terminator, so
63 : : * the buffer is always a valid C string up to that point. Bytes that lie
64 : : * beyond the terminator are not initialized by this function — pass
65 : : * @ref ZERO_NEW_MEMORY if newly exposed bytes should be cleared. If the new
66 : : * logical size leaves less room than the current visible payload, the visible
67 : : * string is truncated to fit and a fresh terminator is written. Descriptors
68 : : * whose reserved byte count no longer covers the current logical payload are
69 : : * rejected instead of being treated as successful no-ops. In this mode
70 : : * `string_length` describes only the visible string prefix, while `length`
71 : : * describes the logical descriptor span in elements. For a tightly packed
72 : : * string they usually differ by one because the terminator occupies the extra
73 : : * slot, but they may differ by much more after proactive reserve growth. For
74 : : * example, a descriptor that holds `"abc"` normally has `string_length == 3`
75 : : * and `length == 4`; after `m_resize(text,64)` the visible text is still
76 : : * `"abc"` and `string_length` stays `3`, while `length` becomes `64`
77 : : *
78 : : * Without @ref RELEASE_UNUSED the helper reuses the current slab-rounded
79 : : * reserve whenever possible. That includes `m_resize(desc,0)`: for a valid
80 : : * descriptor it clears the logical contents, resets the cached string length,
81 : : * keeps string descriptors in string mode, keeps data descriptors in data mode,
82 : : * and keeps the allocated storage available for later reuse. With
83 : : * @ref RELEASE_UNUSED the helper is allowed to
84 : : * return spare slab-rounded capacity to the allocator during shrink. A resize
85 : : * to zero with @ref RELEASE_UNUSED clears the logical contents, keeps the
86 : : * descriptor in its previous string or data mode, and physically releases the
87 : : * descriptor storage
88 : : *
89 : : * Supported behavior flags (combine with `|`):
90 : : * - @ref ZERO_NEW_MEMORY zero-fills only the bytes that become newly
91 : : * addressable after growth, leaving previously reserved bytes untouched
92 : : * - @ref RELEASE_UNUSED returns spare slab-rounded capacity to the allocator on
93 : : * shrink, and physically frees the block on `m_resize(...,0,RELEASE_UNUSED)`
94 : : *
95 : : * Small example:
96 : : * @code
97 : : * m_create(char,greeting,MEMORY_STRING); // empty string descriptor
98 : : * m_concat_literal(greeting,"Hi"); // length == 3, string_length == 2
99 : : *
100 : : * m_resize(greeting,32); // reserve more room
101 : : * // length == 32, string_length == 2, visible payload still "Hi"
102 : : *
103 : : * m_concat_literal(greeting," there"); // appends without extra reallocation
104 : : *
105 : : * m_resize(greeting,0,RELEASE_UNUSED); // drop contents and release the buffer
106 : : * @endcode
107 : : *
108 : : * @param memory_structure Descriptor to resize
109 : : * @param new_count Requested logical size in elements
110 : : * @param behavior_flags Resize behavior mask. Pass `0`, a single
111 : : * @ref RESIZEMODES flag, or any bitwise OR of flags
112 : : * @return `SUCCESS` on success; `FAILURE` otherwise. Failures are reported through @ref report for easier diagnostics
113 : : *
114 : : * @pre @p memory_structure must be non-NULL and initialized
115 : : * (@ref memory::single_element_size must be non-zero)
116 : : * @pre A valid descriptor must not advertise `length > 0` while
117 : : * @ref memory::data is `NULL`
118 : : * @pre A valid descriptor must not advertise
119 : : * @ref memory::actually_allocated_bytes greater than `0` while
120 : : * @ref memory::data is `NULL`
121 : : * @pre A valid descriptor must keep
122 : : * @ref memory::actually_allocated_bytes large enough to cover the
123 : : * current logical payload
124 : : * @pre Data descriptors (`is_string == false`) must already have
125 : : * @ref memory::string_length equal to `0`
126 : : *
127 : : * @warning Any physical reallocation may move @ref memory::data, so refresh cached raw pointers after a successful resize
128 : : */
129 : 74649 : Return mem_resize(
130 : : memory *memory_structure,
131 : : size_t new_count,
132 : : RESIZEMODES behavior_flags)
133 : : {
134 : : /* Status returned by this function through provide()
135 : : Default value assumes successful completion */
136 : 74649 : Return status = SUCCESS;
137 : :
138 : : /* Remembers whether bytes that become newly reachable after growth must be zero-filled */
139 : 74649 : const bool zero_new_memory = (behavior_flags & ZERO_NEW_MEMORY) != 0;
140 : :
141 : : /* Remembers whether shrink operations may return spare slab-rounded reserve to the allocator */
142 : 74649 : const bool allow_shrink = (behavior_flags & RELEASE_UNUSED) != 0;
143 : :
144 [ + - - + ]: 74649 : if(memory_structure == NULL || memory_structure->single_element_size == 0)
145 : : {
146 : 0 : report("Memory management; Descriptor is NULL or not initialized");
147 : 0 : provide(FAILURE);
148 : : }
149 : :
150 [ + + + + ]: 74649 : if(memory_structure->length > 0 && memory_structure->data == NULL)
151 : : {
152 : 1 : report("Memory management; Descriptor has non-zero length with NULL data pointer");
153 : 1 : provide(FAILURE);
154 : : }
155 : :
156 [ + + + + ]: 74648 : if(memory_structure->is_string == false && memory_structure->string_length != 0)
157 : : {
158 : 1 : report("Memory management; Data descriptor has non-zero string_length during resize");
159 : 1 : provide(FAILURE);
160 : : }
161 : :
162 : : /* Byte size of the payload described by the descriptor before this resize starts */
163 : 74647 : size_t previous_payload_bytes = 0;
164 : :
165 : : /* Total bytes the allocator says are currently reserved for the descriptor */
166 : 74647 : size_t previous_allocated_bytes = 0;
167 : :
168 : : /* Element count visible to callers before any resize logic runs */
169 : 74647 : size_t previous_length = 0;
170 : :
171 : : /* Old block-overhead bytes beyond the logical payload, used only for telemetry accounting */
172 : 74647 : size_t previous_block_overhead = 0;
173 : :
174 : : /* Byte size needed for the requested new_count payload */
175 : 74647 : size_t total_size_in_bytes = 0;
176 : :
177 : : /* Cached visible string length captured before resize mutates any metadata */
178 : 74647 : size_t previous_string_length = 0;
179 : :
180 : 74647 : previous_length = memory_structure->length;
181 : 74647 : previous_allocated_bytes = memory_structure->actually_allocated_bytes;
182 : 74647 : previous_string_length = memory_structure->string_length;
183 : :
184 : 74647 : run(mem_guarded_byte_size(memory_structure,previous_length,&previous_payload_bytes));
185 : :
186 [ + + ]: 74647 : if(previous_allocated_bytes > previous_payload_bytes)
187 : : {
188 : : /* Direct subtraction is safe because the if-guard above proves no underflow */
189 : 52251 : previous_block_overhead = previous_allocated_bytes - previous_payload_bytes;
190 : : }
191 : :
192 [ + - + + ]: 74647 : if((TRIUMPH & status) &&
193 : 52256 : previous_allocated_bytes > 0 &&
194 [ - + ]: 52256 : memory_structure->data == NULL)
195 : : {
196 : 0 : report("Memory management; Descriptor has reserved bytes with NULL data pointer during resize");
197 : 0 : provide(FAILURE);
198 : : }
199 : :
200 [ + - + + ]: 74647 : if((TRIUMPH & status) &&
201 : 52253 : previous_length > 0 &&
202 [ + + ]: 52253 : previous_allocated_bytes < previous_payload_bytes)
203 : : {
204 : 2 : report("Memory management; Descriptor reserve is smaller than logical payload during resize");
205 : 2 : provide(FAILURE);
206 : : }
207 : :
208 [ + - ]: 74645 : if((TRIUMPH & status) &&
209 [ + + + + ]: 74645 : memory_structure->is_string == true &&
210 [ + + ]: 51665 : previous_length > 0 &&
211 : : previous_string_length >= previous_length)
212 : : {
213 : 1 : report("Memory management; String descriptor cache is inconsistent during resize");
214 : 1 : provide(FAILURE);
215 : : }
216 : :
217 [ + - ]: 74644 : if((TRIUMPH & status) &&
218 [ + + + + ]: 74644 : memory_structure->is_string == true &&
219 [ - + ]: 20717 : previous_length == 0 &&
220 : : previous_string_length != 0)
221 : : {
222 : 0 : report("Memory management; String descriptor has non-zero string_length with zero length during resize");
223 : 0 : provide(FAILURE);
224 : : }
225 : :
226 [ + - + + ]: 74644 : if((TRIUMPH & status) && new_count == previous_length)
227 : : {
228 [ + + + + : 1783 : if(new_count != 0 || allow_shrink == false || previous_allocated_bytes == 0)
- + ]
229 : : {
230 : 1781 : telemetry_noop_resizes();
231 : 1781 : telemetry_consecutive_noop_resizes_advanced();
232 : 1781 : provide(status);
233 : : }
234 : : }
235 : :
236 : 72863 : telemetry_current_consecutive_noop_resizes_reset();
237 : :
238 [ + - ]: 72863 : if(TRIUMPH & status)
239 : : {
240 : 72863 : call(mem_guarded_byte_size(memory_structure,new_count,&total_size_in_bytes));
241 : : }
242 : :
243 [ - + ]: 72863 : if(CRITICAL & status)
244 : : {
245 : 0 : report("Memory management; Overflow for length=%zu (single_element_size=%zu)",new_count,memory_structure->single_element_size);
246 : : }
247 : :
248 [ + - ]: 72863 : if(TRIUMPH & status)
249 : : {
250 [ + + ]: 72863 : if(new_count == 0)
251 : : {
252 [ + + + - ]: 11 : if(allow_shrink == true && previous_allocated_bytes > 0)
253 : : {
254 : 2 : telemetry_release_unused_shrinks();
255 : 2 : telemetry_total_release_unused_heap_reserved_bytes_released(previous_allocated_bytes);
256 : :
257 : 2 : run(mem_delete(memory_structure));
258 : : } else {
259 [ + - ]: 9 : if(previous_payload_bytes > 0)
260 : : {
261 : 9 : telemetry_current_payload_bytes_removed(previous_payload_bytes);
262 : 9 : telemetry_block_overhead_bytes_added(previous_payload_bytes);
263 : : }
264 : :
265 : 9 : memory_structure->length = 0;
266 : 9 : memory_structure->string_length = 0;
267 : :
268 [ + + ]: 9 : if(memory_structure->is_string == true &&
269 [ + - ]: 5 : memory_structure->data != NULL &&
270 [ + - ]: 5 : memory_structure->actually_allocated_bytes >= memory_structure->single_element_size)
271 : : {
272 : 5 : run(mem_write_zero_terminator(memory_structure,0));
273 : : }
274 : : }
275 : : } else {
276 : : /* Requested payload rounded up to the slab-style block size */
277 : 72852 : size_t slab_size_in_bytes = 0;
278 : :
279 [ - + ]: 72852 : if(round_up_to_block_size(total_size_in_bytes,&slab_size_in_bytes) != 0)
280 : : {
281 : 0 : report("Memory management; Slab-size rounding overflow for %zu bytes",total_size_in_bytes);
282 : 0 : provide(FAILURE);
283 : : } else {
284 : : /* True when the descriptor has no allocation yet and needs its first buffer */
285 : 72852 : const bool needs_fresh_allocation = memory_structure->data == NULL;
286 : : /* True when the slab-rounded target size is larger than the currently reserved block */
287 : 72852 : const bool needs_growth = slab_size_in_bytes > memory_structure->actually_allocated_bytes;
288 : : /* True when RELEASE_UNUSED allows an immediate shrink of the reserved block */
289 [ + + ]: 72885 : const bool should_shrink = (allow_shrink == true) &&
290 [ + + ]: 33 : slab_size_in_bytes < memory_structure->actually_allocated_bytes;
291 : :
292 [ + + + + : 72852 : if((needs_fresh_allocation == true) || (needs_growth == true) || (should_shrink == true))
+ + ]
293 : 22311 : {
294 : : /* Result pointer returned by malloc()/realloc() before it is committed to the descriptor */
295 : 22311 : void *resized_pointer = NULL;
296 : :
297 [ + + ]: 22311 : if(needs_fresh_allocation == true)
298 : : {
299 : 22278 : resized_pointer = malloc(slab_size_in_bytes);
300 : : } else {
301 : 33 : resized_pointer = realloc(memory_structure->data,slab_size_in_bytes);
302 : : }
303 : :
304 [ + + ]: 22311 : if(resized_pointer == NULL)
305 : : {
306 : 2 : report("Memory management; Memory allocation failed for %zu bytes",slab_size_in_bytes);
307 : 2 : status = FAILURE;
308 : :
309 [ + + ]: 2 : if(needs_fresh_allocation == true)
310 : : {
311 : 1 : telemetry_heap_allocation_failures();
312 : : } else {
313 : 1 : telemetry_heap_reallocation_failures();
314 : : }
315 : :
316 : : } else {
317 : 22309 : memory_structure->data = resized_pointer;
318 : :
319 [ + + ]: 22309 : if(needs_fresh_allocation == true)
320 : : {
321 : 22277 : telemetry_active_descriptors_acquired();
322 : 22277 : telemetry_fresh_heap_allocations();
323 : :
324 [ + - ]: 22277 : if(slab_size_in_bytes > 0)
325 : : {
326 : 22277 : telemetry_heap_reserved_bytes_acquired(slab_size_in_bytes);
327 : : }
328 [ + + ]: 32 : } else if(needs_growth == true){
329 : : /* Direct subtraction is safe because needs_growth implies slab_size_in_bytes > previous_allocated_bytes */
330 : 28 : const size_t added_bytes = slab_size_in_bytes - previous_allocated_bytes;
331 : :
332 : 28 : telemetry_heap_reallocations();
333 : :
334 [ + - ]: 28 : if(added_bytes > 0)
335 : : {
336 : 28 : telemetry_heap_reserved_bytes_acquired(added_bytes);
337 : : }
338 [ + - ]: 4 : } else if(should_shrink == true){
339 : : /* Direct subtraction is safe because should_shrink implies slab_size_in_bytes < previous_allocated_bytes */
340 : 4 : const size_t reclaimed_bytes = previous_allocated_bytes - slab_size_in_bytes;
341 : :
342 : 4 : telemetry_heap_reallocations();
343 : :
344 [ + - ]: 4 : if(reclaimed_bytes > 0)
345 : : {
346 : 4 : telemetry_release_unused_shrinks();
347 : 4 : telemetry_total_release_unused_heap_reserved_bytes_released(reclaimed_bytes);
348 : 4 : telemetry_current_heap_reserved_bytes_released(reclaimed_bytes);
349 : 4 : telemetry_total_heap_reserved_bytes_released(reclaimed_bytes);
350 : : }
351 : : }
352 : :
353 : 22309 : memory_structure->actually_allocated_bytes = slab_size_in_bytes;
354 : : }
355 : : } else {
356 : 50541 : telemetry_in_place_resizes();
357 : : }
358 : :
359 [ + + ]: 72852 : if(TRIUMPH & status)
360 : : {
361 : : /* Newly reachable payload bytes that must be cleared for ZERO_NEW_MEMORY */
362 : 72850 : size_t bytes_to_zero = 0;
363 : :
364 [ + + + + ]: 72850 : if((zero_new_memory == true) && total_size_in_bytes > previous_payload_bytes)
365 : : {
366 : : /* Direct subtraction is safe because the if-guard above proves no underflow */
367 : 1277 : bytes_to_zero = total_size_in_bytes - previous_payload_bytes;
368 : : }
369 : :
370 [ + + ]: 72850 : if(bytes_to_zero > 0)
371 : : {
372 : : /* Writable byte view used by memset() when zero-filling fresh payload space */
373 : 1277 : unsigned char *memory_structure_data_rewritable = (unsigned char *)memory_structure->data;
374 : :
375 [ - + ]: 1277 : if(memory_structure_data_rewritable == NULL)
376 : : {
377 : 0 : report("Memory management; Data pointer is NULL during zero-fill");
378 : 0 : provide(FAILURE);
379 : : } else {
380 : 1277 : memset(memory_structure_data_rewritable + previous_payload_bytes,0,bytes_to_zero);
381 : 1277 : telemetry_zero_initialized_payload_growths();
382 : : }
383 : : }
384 : :
385 [ + - ]: 72850 : if(TRIUMPH & status)
386 : : {
387 : : /* Change in logical payload bytes, used only for telemetry bookkeeping */
388 : 72850 : size_t payload_delta = 0;
389 : :
390 [ + + ]: 72850 : if(total_size_in_bytes > previous_payload_bytes)
391 : : {
392 : : /* Direct subtraction is safe because the if-guard above proves no underflow */
393 : 67427 : payload_delta = total_size_in_bytes - previous_payload_bytes;
394 : :
395 [ + - ]: 67427 : if(payload_delta > 0)
396 : : {
397 : 67427 : telemetry_payload_bytes_added(payload_delta);
398 : : }
399 [ + - ]: 5423 : } else if(total_size_in_bytes < previous_payload_bytes){
400 : : /* Direct subtraction is safe because the else-if guard proves no underflow */
401 : 5423 : payload_delta = previous_payload_bytes - total_size_in_bytes;
402 : :
403 [ + - ]: 5423 : if(payload_delta > 0)
404 : : {
405 : 5423 : telemetry_current_payload_bytes_removed(payload_delta);
406 : : }
407 : : }
408 : : }
409 : :
410 : : /* Growing a string changes only available capacity. The visible
411 : : payload is not rewritten here, so helper-driven normalization
412 : : may legitimately become a no-op and rely on the existing
413 : : zero terminator that all string mutators must preserve */
414 [ + - + + ]: 72850 : if((TRIUMPH & status) && memory_structure->is_string == true)
415 : 70607 : {
416 : : /* Visible string length that should remain after resize finishes and terminator is normalized */
417 : 70607 : size_t resulting_string_length = 0;
418 : :
419 [ + + ]: 70607 : if(previous_length > 0)
420 : : {
421 : 50004 : resulting_string_length = previous_string_length;
422 : :
423 [ + + ]: 50004 : if(resulting_string_length >= new_count)
424 : : {
425 : 5206 : resulting_string_length = new_count - 1;
426 : : }
427 : : }
428 : :
429 : 70607 : memory_structure->length = new_count;
430 : 70607 : memory_structure->string_length = resulting_string_length;
431 : 70607 : run(mem_string_truncate(memory_structure,resulting_string_length));
432 [ + - ]: 2243 : } else if(TRIUMPH & status){
433 : 2243 : memory_structure->length = new_count;
434 : 2243 : memory_structure->string_length = 0;
435 : : }
436 : : }
437 : : }
438 : : }
439 : : }
440 : :
441 [ + + + + ]: 72863 : if((TRIUMPH & status) && new_count != 0)
442 : : {
443 : : /* Final reserve size after resize logic, used to recalculate block overhead */
444 : 72850 : const size_t resulting_allocated_bytes = memory_structure->actually_allocated_bytes;
445 : : /* New block-overhead bytes beyond the logical payload after the resize completes */
446 : 72850 : size_t new_block_overhead = 0;
447 : :
448 [ + + ]: 72850 : if(resulting_allocated_bytes > total_size_in_bytes)
449 : : {
450 : : /* Direct subtraction is safe because the if-guard above proves no underflow */
451 : 72846 : new_block_overhead = resulting_allocated_bytes - total_size_in_bytes;
452 : : }
453 : :
454 [ + + ]: 72850 : if(new_block_overhead > previous_block_overhead)
455 : : {
456 : : /* Direct subtraction is safe because the if-guard above proves no underflow */
457 : 27714 : const size_t block_overhead_delta = new_block_overhead - previous_block_overhead;
458 : :
459 [ + - ]: 27714 : if(block_overhead_delta > 0)
460 : : {
461 : 27714 : telemetry_block_overhead_bytes_added(block_overhead_delta);
462 : : }
463 [ + + ]: 45136 : } else if(new_block_overhead < previous_block_overhead){
464 : : /* Direct subtraction is safe because the else-if guard proves no underflow */
465 : 45130 : const size_t block_overhead_delta = previous_block_overhead - new_block_overhead;
466 : :
467 [ + - ]: 45130 : if(block_overhead_delta > 0)
468 : : {
469 : 45130 : telemetry_current_block_overhead_bytes_removed(block_overhead_delta);
470 : : }
471 : : }
472 : : }
473 : :
474 : 72863 : provide(status);
475 : : }
|