16
16
17
17
#include "o1heap.h"
18
18
#include <assert.h>
19
+ #include <limits.h>
19
20
20
21
// ---------------------------------------- BUILD CONFIGURATION OPTIONS ----------------------------------------
21
22
32
33
# define O1HEAP_ASSERT (x ) assert(x) // NOSONAR
33
34
#endif
34
35
36
+ /// Allow usage of compiler intrinsics for branch annotation and CLZ.
37
+ #ifndef O1HEAP_USE_INTRINSICS
38
+ # define O1HEAP_USE_INTRINSICS 1
39
+ #endif
40
+
35
41
/// Branch probability annotations are used to improve the worst case execution time (WCET). They are entirely optional.
36
- #ifndef O1HEAP_LIKELY
42
+ #if O1HEAP_USE_INTRINSICS && !defined( O1HEAP_LIKELY )
37
43
# if defined(__GNUC__ ) || defined(__clang__ ) || defined(__CC_ARM )
38
44
// Intentional violation of MISRA: branch hinting macro cannot be replaced with a function definition.
39
45
# define O1HEAP_LIKELY (x ) __builtin_expect((x), 1) // NOSONAR
40
- # else
41
- # define O1HEAP_LIKELY (x ) x
42
46
# endif
43
47
#endif
48
+ #ifndef O1HEAP_LIKELY
49
+ # define O1HEAP_LIKELY (x ) x
50
+ #endif
44
51
45
52
/// This option is used for testing only. Do not use in production.
46
53
#ifndef O1HEAP_PRIVATE
47
54
# define O1HEAP_PRIVATE static inline
48
55
#endif
49
56
57
+ /// Count leading zeros (CLZ) is used for fast computation of binary logarithm (which needs to be done very often).
58
+ /// Most of the modern processors (including the embedded ones) implement dedicated hardware support for fast CLZ
59
+ /// computation, which is available via compiler intrinsics. The default implementation will automatically use
60
+ /// the intrinsics for some of the compilers; for others it will default to the slow software emulation,
61
+ /// which can be overridden by the user via O1HEAP_CONFIG_HEADER. The library guarantees that the argument is positive.
62
+ #if O1HEAP_USE_INTRINSICS && !defined(O1HEAP_CLZ )
63
+ # if defined(__GNUC__ ) || defined(__clang__ ) || defined(__CC_ARM )
64
+ # define O1HEAP_CLZ __builtin_clzl
65
+ # endif
66
+ #endif
67
+ #ifndef O1HEAP_CLZ
68
+ O1HEAP_PRIVATE uint_fast8_t O1HEAP_CLZ (const size_t x )
69
+ {
70
+ O1HEAP_ASSERT (x > 0 );
71
+ size_t t = ((size_t ) 1U ) << ((sizeof (size_t ) * CHAR_BIT ) - 1U );
72
+ uint_fast8_t r = 0 ;
73
+ while ((x & t ) == 0 )
74
+ {
75
+ t >>= 1U ;
76
+ r ++ ;
77
+ }
78
+ return r ;
79
+ }
80
+ #endif
81
+
50
82
// ---------------------------------------- INTERNAL DEFINITIONS ----------------------------------------
51
83
52
84
#if !defined(__STDC_VERSION__ ) || (__STDC_VERSION__ < 199901L )
72
104
73
105
/// Normally we should subtract log2(FRAGMENT_SIZE_MIN) but log2 is bulky to compute using the preprocessor only.
74
106
/// We will certainly end up with unused bins this way, but it is cheap to ignore.
75
- #define NUM_BINS_MAX (sizeof(size_t) * 8U )
107
+ #define NUM_BINS_MAX (sizeof(size_t) * CHAR_BIT )
76
108
77
109
static_assert ((O1HEAP_ALIGNMENT & (O1HEAP_ALIGNMENT - 1U )) == 0U , "Not a power of 2" );
78
110
static_assert ((FRAGMENT_SIZE_MIN & (FRAGMENT_SIZE_MIN - 1U )) == 0U , "Not a power of 2" );
@@ -113,41 +145,37 @@ struct O1HeapInstance
113
145
static_assert (INSTANCE_SIZE_PADDED >= sizeof (O1HeapInstance ), "Invalid instance footprint computation" );
114
146
static_assert ((INSTANCE_SIZE_PADDED % O1HEAP_ALIGNMENT ) == 0U , "Invalid instance footprint computation" );
115
147
116
- /// True if the argument is an integer power of two or zero .
117
- O1HEAP_PRIVATE bool isPowerOf2 (const size_t x )
148
+ /// Undefined for zero argument.
149
+ O1HEAP_PRIVATE uint_fast8_t log2Floor (const size_t x )
118
150
{
119
- return (x & (x - 1U )) == 0U ;
151
+ O1HEAP_ASSERT (x > 0 );
152
+ // NOLINTNEXTLINE redundant cast to the same type.
153
+ return (uint_fast8_t ) (((sizeof (x ) * CHAR_BIT ) - 1U ) - ((uint_fast8_t ) O1HEAP_CLZ (x )));
120
154
}
121
155
122
156
/// Special case: if the argument is zero, returns zero.
123
- O1HEAP_PRIVATE uint8_t log2Floor (const size_t x )
157
+ O1HEAP_PRIVATE uint_fast8_t log2Ceil (const size_t x )
124
158
{
125
- size_t tmp = x ;
126
- uint8_t y = 0 ;
127
- // This is currently the only exception to the statement "routines contain neither loops nor recursion".
128
- // It is unclear if there is a better way to compute the binary logarithm than this.
129
- while (tmp > 1U )
130
- {
131
- tmp >>= 1U ;
132
- y ++ ;
133
- }
134
- return y ;
135
- }
136
-
137
- /// Special case: if the argument is zero, returns zero.
138
- O1HEAP_PRIVATE uint8_t log2Ceil (const size_t x )
139
- {
140
- return (uint8_t ) (log2Floor (x ) + (isPowerOf2 (x ) ? 0U : 1U ));
159
+ // NOLINTNEXTLINE redundant cast to the same type.
160
+ return (x <= 1U ) ? 0U : (uint_fast8_t ) ((sizeof (x ) * CHAR_BIT ) - ((uint_fast8_t ) O1HEAP_CLZ (x - 1U )));
141
161
}
142
162
143
163
/// Raise 2 into the specified power.
144
164
/// You might be tempted to do something like (1U << power). WRONG! We humans are prone to forgetting things.
145
165
/// If you forget to cast your 1U to size_t or ULL, you may end up with undefined behavior.
146
- O1HEAP_PRIVATE size_t pow2 (const uint8_t power )
166
+ O1HEAP_PRIVATE size_t pow2 (const uint_fast8_t power )
147
167
{
148
168
return ((size_t ) 1U ) << power ;
149
169
}
150
170
171
+ /// This is equivalent to pow2(log2Ceil(x)). Undefined for x<2.
172
+ O1HEAP_PRIVATE size_t roundUpToPowerOf2 (const size_t x )
173
+ {
174
+ O1HEAP_ASSERT (x >= 2U );
175
+ // NOLINTNEXTLINE redundant cast to the same type.
176
+ return ((size_t ) 1U ) << ((sizeof (x ) * CHAR_BIT ) - ((uint_fast8_t ) O1HEAP_CLZ (x - 1U )));
177
+ }
178
+
151
179
/// Links two fragments so that their next/prev pointers point to each other; left goes before right.
152
180
O1HEAP_PRIVATE void interlink (Fragment * const left , Fragment * const right )
153
181
{
@@ -168,7 +196,7 @@ O1HEAP_PRIVATE void rebin(O1HeapInstance* const handle, Fragment* const fragment
168
196
O1HEAP_ASSERT (fragment != NULL );
169
197
O1HEAP_ASSERT (fragment -> header .size >= FRAGMENT_SIZE_MIN );
170
198
O1HEAP_ASSERT ((fragment -> header .size % FRAGMENT_SIZE_MIN ) == 0U );
171
- const uint8_t idx = log2Floor (fragment -> header .size / FRAGMENT_SIZE_MIN ); // Round DOWN when inserting.
199
+ const uint_fast8_t idx = log2Floor (fragment -> header .size / FRAGMENT_SIZE_MIN ); // Round DOWN when inserting.
172
200
O1HEAP_ASSERT (idx < NUM_BINS_MAX );
173
201
// Add the new fragment to the beginning of the bin list.
174
202
// I.e., each allocation will be returning the most-recently-used fragment -- good for caching.
@@ -189,7 +217,7 @@ O1HEAP_PRIVATE void unbin(O1HeapInstance* const handle, const Fragment* const fr
189
217
O1HEAP_ASSERT (fragment != NULL );
190
218
O1HEAP_ASSERT (fragment -> header .size >= FRAGMENT_SIZE_MIN );
191
219
O1HEAP_ASSERT ((fragment -> header .size % FRAGMENT_SIZE_MIN ) == 0U );
192
- const uint8_t idx = log2Floor (fragment -> header .size / FRAGMENT_SIZE_MIN ); // Round DOWN when removing.
220
+ const uint_fast8_t idx = log2Floor (fragment -> header .size / FRAGMENT_SIZE_MIN ); // Round DOWN when removing.
193
221
O1HEAP_ASSERT (idx < NUM_BINS_MAX );
194
222
// Remove the bin from the free fragment list.
195
223
if (O1HEAP_LIKELY (fragment -> next_free != NULL ))
@@ -244,7 +272,7 @@ O1HeapInstance* o1heapInit(void* const base, const size_t size)
244
272
O1HEAP_ASSERT ((capacity >= FRAGMENT_SIZE_MIN ) && (capacity <= FRAGMENT_SIZE_MAX ));
245
273
246
274
// Initialize the root fragment.
247
- Fragment * const frag = (Fragment * ) (void * ) (((uint8_t * ) base ) + INSTANCE_SIZE_PADDED );
275
+ Fragment * const frag = (Fragment * ) (void * ) (((char * ) base ) + INSTANCE_SIZE_PADDED );
248
276
O1HEAP_ASSERT ((((size_t ) frag ) % O1HEAP_ALIGNMENT ) == 0U );
249
277
frag -> header .next = NULL ;
250
278
frag -> header .prev = NULL ;
@@ -279,13 +307,13 @@ void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount)
279
307
{
280
308
// Add the header size and align the allocation size to the power of 2.
281
309
// See "Timing-Predictable Memory Allocation In Hard Real-Time Systems", Herter, page 27.
282
- const size_t fragment_size = pow2 ( log2Ceil ( amount + O1HEAP_ALIGNMENT ) );
310
+ const size_t fragment_size = roundUpToPowerOf2 ( amount + O1HEAP_ALIGNMENT );
283
311
O1HEAP_ASSERT (fragment_size <= FRAGMENT_SIZE_MAX );
284
312
O1HEAP_ASSERT (fragment_size >= FRAGMENT_SIZE_MIN );
285
313
O1HEAP_ASSERT (fragment_size >= amount + O1HEAP_ALIGNMENT );
286
- O1HEAP_ASSERT (isPowerOf2 (fragment_size ));
314
+ O1HEAP_ASSERT ((fragment_size & ( fragment_size - 1U )) == 0U ); // Is power of 2.
287
315
288
- const uint8_t optimal_bin_index = log2Ceil (fragment_size / FRAGMENT_SIZE_MIN ); // Use CEIL when fetching.
316
+ const uint_fast8_t optimal_bin_index = log2Ceil (fragment_size / FRAGMENT_SIZE_MIN ); // Use CEIL when fetching.
289
317
O1HEAP_ASSERT (optimal_bin_index < NUM_BINS_MAX );
290
318
const size_t candidate_bin_mask = ~(pow2 (optimal_bin_index ) - 1U );
291
319
@@ -294,8 +322,8 @@ void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount)
294
322
const size_t smallest_bin_mask = suitable_bins & ~(suitable_bins - 1U ); // Clear all bits but the lowest.
295
323
if (O1HEAP_LIKELY (smallest_bin_mask != 0 ))
296
324
{
297
- O1HEAP_ASSERT (isPowerOf2 (smallest_bin_mask ));
298
- const uint8_t bin_index = log2Floor (smallest_bin_mask );
325
+ O1HEAP_ASSERT ((smallest_bin_mask & ( smallest_bin_mask - 1U )) == 0U ); // Is power of 2.
326
+ const uint_fast8_t bin_index = log2Floor (smallest_bin_mask );
299
327
O1HEAP_ASSERT (bin_index >= optimal_bin_index );
300
328
O1HEAP_ASSERT (bin_index < NUM_BINS_MAX );
301
329
@@ -314,7 +342,7 @@ void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount)
314
342
O1HEAP_ASSERT (leftover % FRAGMENT_SIZE_MIN == 0U ); // Alignment check.
315
343
if (O1HEAP_LIKELY (leftover >= FRAGMENT_SIZE_MIN ))
316
344
{
317
- Fragment * const new_frag = (Fragment * ) (void * ) (((uint8_t * ) frag ) + fragment_size );
345
+ Fragment * const new_frag = (Fragment * ) (void * ) (((char * ) frag ) + fragment_size );
318
346
O1HEAP_ASSERT (((size_t ) new_frag ) % O1HEAP_ALIGNMENT == 0U );
319
347
new_frag -> header .size = leftover ;
320
348
new_frag -> header .used = false;
@@ -336,7 +364,7 @@ void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount)
336
364
O1HEAP_ASSERT (frag -> header .size >= amount + O1HEAP_ALIGNMENT );
337
365
frag -> header .used = true;
338
366
339
- out = ((uint8_t * ) frag ) + O1HEAP_ALIGNMENT ;
367
+ out = ((char * ) frag ) + O1HEAP_ALIGNMENT ;
340
368
}
341
369
}
342
370
@@ -359,7 +387,7 @@ void o1heapFree(O1HeapInstance* const handle, void* const pointer)
359
387
O1HEAP_ASSERT (handle -> diagnostics .capacity <= FRAGMENT_SIZE_MAX );
360
388
if (O1HEAP_LIKELY (pointer != NULL )) // NULL pointer is a no-op.
361
389
{
362
- Fragment * const frag = (Fragment * ) (void * ) (((uint8_t * ) pointer ) - O1HEAP_ALIGNMENT );
390
+ Fragment * const frag = (Fragment * ) (void * ) (((char * ) pointer ) - O1HEAP_ALIGNMENT );
363
391
364
392
// Check for heap corruption in debug builds.
365
393
O1HEAP_ASSERT (((size_t ) frag ) % sizeof (Fragment * ) == 0U );
@@ -429,7 +457,7 @@ bool o1heapDoInvariantsHold(const O1HeapInstance* const handle)
429
457
// Check the bin mask consistency.
430
458
for (size_t i = 0 ; i < NUM_BINS_MAX ; i ++ ) // Dear compiler, feel free to unroll this loop.
431
459
{
432
- const bool mask_bit_set = (handle -> nonempty_bin_mask & pow2 ((uint8_t ) i )) != 0U ;
460
+ const bool mask_bit_set = (handle -> nonempty_bin_mask & pow2 ((uint_fast8_t ) i )) != 0U ;
433
461
const bool bin_nonempty = handle -> bins [i ] != NULL ;
434
462
valid = valid && (mask_bit_set == bin_nonempty );
435
463
}
0 commit comments