1 /*
2 * Copyright (c) 2019 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #include <sys/sys_heap.h>
7 #include <kernel.h>
8 #include "heap.h"
9
10 /* White-box sys_heap validation code. Uses internal data structures.
11 * Not expected to be useful in production apps. This checks every
12 * header field of every chunk and returns true if the totality of the
13 * data structure is a valid heap. It doesn't necessarily tell you
14 * that it is the CORRECT heap given the history of alloc/free calls
15 * that it can't inspect. In a pathological case, you can imagine
16 * something scribbling a copy of a previously-valid heap on top of a
17 * running one and corrupting it. YMMV.
18 */
19
20 #define VALIDATE(cond) do { if (!(cond)) { return false; } } while (0)
21
in_bounds(struct z_heap * h,chunkid_t c)22 static bool in_bounds(struct z_heap *h, chunkid_t c)
23 {
24 VALIDATE(c >= right_chunk(h, 0));
25 VALIDATE(c < h->end_chunk);
26 VALIDATE(chunk_size(h, c) < h->end_chunk);
27 return true;
28 }
29
valid_chunk(struct z_heap * h,chunkid_t c)30 static bool valid_chunk(struct z_heap *h, chunkid_t c)
31 {
32 VALIDATE(chunk_size(h, c) > 0);
33 VALIDATE(c + chunk_size(h, c) <= h->end_chunk);
34 VALIDATE(in_bounds(h, c));
35 VALIDATE(right_chunk(h, left_chunk(h, c)) == c);
36 VALIDATE(left_chunk(h, right_chunk(h, c)) == c);
37 if (chunk_used(h, c)) {
38 VALIDATE(!solo_free_header(h, c));
39 } else {
40 VALIDATE(chunk_used(h, left_chunk(h, c)));
41 VALIDATE(chunk_used(h, right_chunk(h, c)));
42 if (!solo_free_header(h, c)) {
43 VALIDATE(in_bounds(h, prev_free_chunk(h, c)));
44 VALIDATE(in_bounds(h, next_free_chunk(h, c)));
45 }
46 }
47 return true;
48 }
49
50 /* Validate multiple state dimensions for the bucket "next" pointer
51 * and see that they match. Probably should unify the design a
52 * bit...
53 */
check_nexts(struct z_heap * h,int bidx)54 static inline void check_nexts(struct z_heap *h, int bidx)
55 {
56 struct z_heap_bucket *b = &h->buckets[bidx];
57
58 bool emptybit = (h->avail_buckets & (1 << bidx)) == 0;
59 bool emptylist = b->next == 0;
60 bool empties_match = emptybit == emptylist;
61
62 (void)empties_match;
63 CHECK(empties_match);
64
65 if (b->next != 0) {
66 CHECK(valid_chunk(h, b->next));
67 }
68 }
69
sys_heap_validate(struct sys_heap * heap)70 bool sys_heap_validate(struct sys_heap *heap)
71 {
72 struct z_heap *h = heap->heap;
73 chunkid_t c;
74
75 /*
76 * Walk through the chunks linearly, verifying sizes and end pointer.
77 */
78 for (c = right_chunk(h, 0); c < h->end_chunk; c = right_chunk(h, c)) {
79 if (!valid_chunk(h, c)) {
80 return false;
81 }
82 }
83 if (c != h->end_chunk) {
84 return false; /* Should have exactly consumed the buffer */
85 }
86
87 /* Check the free lists: entry count should match, empty bit
88 * should be correct, and all chunk entries should point into
89 * valid unused chunks. Mark those chunks USED, temporarily.
90 */
91 for (int b = 0; b <= bucket_idx(h, h->end_chunk); b++) {
92 chunkid_t c0 = h->buckets[b].next;
93 uint32_t n = 0;
94
95 check_nexts(h, b);
96
97 for (c = c0; c != 0 && (n == 0 || c != c0);
98 n++, c = next_free_chunk(h, c)) {
99 if (!valid_chunk(h, c)) {
100 return false;
101 }
102 set_chunk_used(h, c, true);
103 }
104
105 bool empty = (h->avail_buckets & (1 << b)) == 0;
106 bool zero = n == 0;
107
108 if (empty != zero) {
109 return false;
110 }
111
112 if (empty && h->buckets[b].next != 0) {
113 return false;
114 }
115 }
116
117 /*
118 * Walk through the chunks linearly again, verifying that all chunks
119 * but solo headers are now USED (i.e. all free blocks were found
120 * during enumeration). Mark all such blocks UNUSED and solo headers
121 * USED.
122 */
123 chunkid_t prev_chunk = 0;
124 for (c = right_chunk(h, 0); c < h->end_chunk; c = right_chunk(h, c)) {
125 if (!chunk_used(h, c) && !solo_free_header(h, c)) {
126 return false;
127 }
128 if (left_chunk(h, c) != prev_chunk) {
129 return false;
130 }
131 prev_chunk = c;
132
133 set_chunk_used(h, c, solo_free_header(h, c));
134 }
135 if (c != h->end_chunk) {
136 return false; /* Should have exactly consumed the buffer */
137 }
138
139 /* Go through the free lists again checking that the linear
140 * pass caught all the blocks and that they now show UNUSED.
141 * Mark them USED.
142 */
143 for (int b = 0; b <= bucket_idx(h, h->end_chunk); b++) {
144 chunkid_t c0 = h->buckets[b].next;
145 int n = 0;
146
147 if (c0 == 0) {
148 continue;
149 }
150
151 for (c = c0; n == 0 || c != c0; n++, c = next_free_chunk(h, c)) {
152 if (chunk_used(h, c)) {
153 return false;
154 }
155 set_chunk_used(h, c, true);
156 }
157 }
158
159 /* Now we are valid, but have managed to invert all the in-use
160 * fields. One more linear pass to fix them up
161 */
162 for (c = right_chunk(h, 0); c < h->end_chunk; c = right_chunk(h, c)) {
163 set_chunk_used(h, c, !chunk_used(h, c));
164 }
165 return true;
166 }
167
168 struct z_heap_stress_rec {
169 void *(*alloc_fn)(void *arg, size_t bytes);
170 void (*free_fn)(void *arg, void *p);
171 void *arg;
172 size_t total_bytes;
173 struct z_heap_stress_block *blocks;
174 size_t nblocks;
175 size_t blocks_alloced;
176 size_t bytes_alloced;
177 uint32_t target_percent;
178 };
179
180 struct z_heap_stress_block {
181 void *ptr;
182 size_t sz;
183 };
184
185 /* Very simple LCRNG (from https://nuclear.llnl.gov/CNP/rng/rngman/node4.html)
186 *
187 * Here to guarantee cross-platform test repeatability.
188 */
rand32(void)189 static uint32_t rand32(void)
190 {
191 static uint64_t state = 123456789; /* seed */
192
193 state = state * 2862933555777941757UL + 3037000493UL;
194
195 return (uint32_t)(state >> 32);
196 }
197
rand_alloc_choice(struct z_heap_stress_rec * sr)198 static bool rand_alloc_choice(struct z_heap_stress_rec *sr)
199 {
200 /* Edge cases: no blocks allocated, and no space for a new one */
201 if (sr->blocks_alloced == 0) {
202 return true;
203 } else if (sr->blocks_alloced >= sr->nblocks) {
204 return false;
205 } else {
206
207 /* The way this works is to scale the chance of choosing to
208 * allocate vs. free such that it's even odds when the heap is
209 * at the target percent, with linear tapering on the low
210 * slope (i.e. we choose to always allocate with an empty
211 * heap, allocate 50% of the time when the heap is exactly at
212 * the target, and always free when above the target). In
213 * practice, the operations aren't quite symmetric (you can
214 * always free, but your allocation might fail), and the units
215 * aren't matched (we're doing math based on bytes allocated
216 * and ignoring the overhead) but this is close enough. And
217 * yes, the math here is coarse (in units of percent), but
218 * that's good enough and fits well inside 32 bit quantities.
219 * (Note precision issue when heap size is above 40MB
220 * though!).
221 */
222 __ASSERT(sr->total_bytes < 0xffffffffU / 100, "too big for u32!");
223 uint32_t full_pct = (100 * sr->bytes_alloced) / sr->total_bytes;
224 uint32_t target = sr->target_percent ? sr->target_percent : 1;
225 uint32_t free_chance = 0xffffffffU;
226
227 if (full_pct < sr->target_percent) {
228 free_chance = full_pct * (0x80000000U / target);
229 }
230
231 return rand32() > free_chance;
232 }
233 }
234
235 /* Chooses a size of block to allocate, logarithmically favoring
236 * smaller blocks (i.e. blocks twice as large are half as frequent
237 */
rand_alloc_size(struct z_heap_stress_rec * sr)238 static size_t rand_alloc_size(struct z_heap_stress_rec *sr)
239 {
240 ARG_UNUSED(sr);
241
242 /* Min scale of 4 means that the half of the requests in the
243 * smallest size have an average size of 8
244 */
245 int scale = 4 + __builtin_clz(rand32());
246
247 return rand32() & ((1 << scale) - 1);
248 }
249
250 /* Returns the index of a randomly chosen block to free */
rand_free_choice(struct z_heap_stress_rec * sr)251 static size_t rand_free_choice(struct z_heap_stress_rec *sr)
252 {
253 return rand32() % sr->blocks_alloced;
254 }
255
256 /* General purpose heap stress test. Takes function pointers to allow
257 * for testing multiple heap APIs with the same rig. The alloc and
258 * free functions are passed back the argument as a context pointer.
259 * The "log" function is for readable user output. The total_bytes
260 * argument should reflect the size of the heap being tested. The
261 * scratch array is used to store temporary state and should be sized
262 * about half as large as the heap itself. Returns true on success.
263 */
sys_heap_stress(void * (* alloc_fn)(void * arg,size_t bytes),void (* free_fn)(void * arg,void * p),void * arg,size_t total_bytes,uint32_t op_count,void * scratch_mem,size_t scratch_bytes,int target_percent,struct z_heap_stress_result * result)264 void sys_heap_stress(void *(*alloc_fn)(void *arg, size_t bytes),
265 void (*free_fn)(void *arg, void *p),
266 void *arg, size_t total_bytes,
267 uint32_t op_count,
268 void *scratch_mem, size_t scratch_bytes,
269 int target_percent,
270 struct z_heap_stress_result *result)
271 {
272 struct z_heap_stress_rec sr = {
273 .alloc_fn = alloc_fn,
274 .free_fn = free_fn,
275 .arg = arg,
276 .total_bytes = total_bytes,
277 .blocks = scratch_mem,
278 .nblocks = scratch_bytes / sizeof(struct z_heap_stress_block),
279 .target_percent = target_percent,
280 };
281
282 *result = (struct z_heap_stress_result) {0};
283
284 for (uint32_t i = 0; i < op_count; i++) {
285 if (rand_alloc_choice(&sr)) {
286 size_t sz = rand_alloc_size(&sr);
287 void *p = sr.alloc_fn(sr.arg, sz);
288
289 result->total_allocs++;
290 if (p != NULL) {
291 result->successful_allocs++;
292 sr.blocks[sr.blocks_alloced].ptr = p;
293 sr.blocks[sr.blocks_alloced].sz = sz;
294 sr.blocks_alloced++;
295 sr.bytes_alloced += sz;
296 }
297 } else {
298 int b = rand_free_choice(&sr);
299 void *p = sr.blocks[b].ptr;
300 size_t sz = sr.blocks[b].sz;
301
302 result->total_frees++;
303 sr.blocks[b] = sr.blocks[sr.blocks_alloced - 1];
304 sr.blocks_alloced--;
305 sr.bytes_alloced -= sz;
306 sr.free_fn(sr.arg, p);
307 }
308 result->accumulated_in_use_bytes += sr.bytes_alloced;
309 }
310 }
311
312 /*
313 * Print heap info for debugging / analysis purpose
314 */
heap_print_info(struct z_heap * h,bool dump_chunks)315 void heap_print_info(struct z_heap *h, bool dump_chunks)
316 {
317 int i, nb_buckets = bucket_idx(h, h->end_chunk) + 1;
318 size_t free_bytes, allocated_bytes, total, overhead;
319
320 printk("Heap at %p contains %d units in %d buckets\n\n",
321 chunk_buf(h), h->end_chunk, nb_buckets);
322
323 printk(" bucket# min units total largest largest\n"
324 " threshold chunks (units) (bytes)\n"
325 " -----------------------------------------------------------\n");
326 for (i = 0; i < nb_buckets; i++) {
327 chunkid_t first = h->buckets[i].next;
328 chunksz_t largest = 0;
329 int count = 0;
330
331 if (first) {
332 chunkid_t curr = first;
333 do {
334 count++;
335 largest = MAX(largest, chunk_size(h, curr));
336 curr = next_free_chunk(h, curr);
337 } while (curr != first);
338 }
339 if (count) {
340 printk("%9d %12d %12d %12d %12zd\n",
341 i, (1 << i) - 1 + min_chunk_size(h), count,
342 largest, chunksz_to_bytes(h, largest));
343 }
344 }
345
346 if (dump_chunks) {
347 printk("\nChunk dump:\n");
348 }
349 free_bytes = allocated_bytes = 0;
350 for (chunkid_t c = 0; ; c = right_chunk(h, c)) {
351 if (chunk_used(h, c)) {
352 if ((c != 0) && (c != h->end_chunk)) {
353 /* 1st and last are always allocated for internal purposes */
354 allocated_bytes += chunksz_to_bytes(h, chunk_size(h, c));
355 }
356 } else {
357 if (!solo_free_header(h, c)) {
358 free_bytes += chunksz_to_bytes(h, chunk_size(h, c));
359 }
360 }
361 if (dump_chunks) {
362 printk("chunk %4d: [%c] size=%-4d left=%-4d right=%d\n",
363 c,
364 chunk_used(h, c) ? '*'
365 : solo_free_header(h, c) ? '.'
366 : '-',
367 chunk_size(h, c),
368 left_chunk(h, c),
369 right_chunk(h, c));
370 }
371 if (c == h->end_chunk) {
372 break;
373 }
374 }
375
376 /* The end marker chunk has a header. It is part of the overhead. */
377 total = h->end_chunk * CHUNK_UNIT + chunk_header_bytes(h);
378 overhead = total - free_bytes - allocated_bytes;
379 printk("\n%zd free bytes, %zd allocated bytes, overhead = %zd bytes (%zd.%zd%%)\n",
380 free_bytes, allocated_bytes, overhead,
381 (1000 * overhead + total/2) / total / 10,
382 (1000 * overhead + total/2) / total % 10);
383 }
384
sys_heap_print_info(struct sys_heap * heap,bool dump_chunks)385 void sys_heap_print_info(struct sys_heap *heap, bool dump_chunks)
386 {
387 heap_print_info(heap->heap, dump_chunks);
388 }
389