xref: /qemu/tests/qtest/libqos/libqos-malloc.c (revision eb5937bad691ed18a401079a0604aa11fea0ecdd)
1292be092SMarc Marí /*
2292be092SMarc Marí  * libqos malloc support
3292be092SMarc Marí  *
4292be092SMarc Marí  * Copyright (c) 2014
5292be092SMarc Marí  *
6292be092SMarc Marí  * Author:
7292be092SMarc Marí  *  John Snow <jsnow@redhat.com>
8292be092SMarc Marí  *
9292be092SMarc Marí  * This work is licensed under the terms of the GNU GPL, version 2 or later.
10292be092SMarc Marí  * See the COPYING file in the top-level directory.
11292be092SMarc Marí  */
12292be092SMarc Marí 
13681c28a3SPeter Maydell #include "qemu/osdep.h"
14292be092SMarc Marí #include "libqos/malloc.h"
15292be092SMarc Marí #include "qemu-common.h"
1687776ab7SPaolo Bonzini #include "qemu/host-utils.h"
17292be092SMarc Marí 
18f6f363c1SJohn Snow typedef struct MemBlock {
19f6f363c1SJohn Snow     QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME;
20f6f363c1SJohn Snow     uint64_t size;
21f6f363c1SJohn Snow     uint64_t addr;
22f6f363c1SJohn Snow } MemBlock;
23f6f363c1SJohn Snow 
24f6f363c1SJohn Snow #define DEFAULT_PAGE_SIZE 4096
25f6f363c1SJohn Snow 
26292be092SMarc Marí static void mlist_delete(MemList *list, MemBlock *node)
27292be092SMarc Marí {
28292be092SMarc Marí     g_assert(list && node);
29292be092SMarc Marí     QTAILQ_REMOVE(list, node, MLIST_ENTNAME);
30292be092SMarc Marí     g_free(node);
31292be092SMarc Marí }
32292be092SMarc Marí 
33292be092SMarc Marí static MemBlock *mlist_find_key(MemList *head, uint64_t addr)
34292be092SMarc Marí {
35292be092SMarc Marí     MemBlock *node;
36292be092SMarc Marí     QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
37292be092SMarc Marí         if (node->addr == addr) {
38292be092SMarc Marí             return node;
39292be092SMarc Marí         }
40292be092SMarc Marí     }
41292be092SMarc Marí     return NULL;
42292be092SMarc Marí }
43292be092SMarc Marí 
44292be092SMarc Marí static MemBlock *mlist_find_space(MemList *head, uint64_t size)
45292be092SMarc Marí {
46292be092SMarc Marí     MemBlock *node;
47292be092SMarc Marí 
48292be092SMarc Marí     QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
49292be092SMarc Marí         if (node->size >= size) {
50292be092SMarc Marí             return node;
51292be092SMarc Marí         }
52292be092SMarc Marí     }
53292be092SMarc Marí     return NULL;
54292be092SMarc Marí }
55292be092SMarc Marí 
56292be092SMarc Marí static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr)
57292be092SMarc Marí {
58292be092SMarc Marí     MemBlock *node;
59292be092SMarc Marí     g_assert(head && insr);
60292be092SMarc Marí 
61292be092SMarc Marí     QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
62292be092SMarc Marí         if (insr->addr < node->addr) {
63292be092SMarc Marí             QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME);
64292be092SMarc Marí             return insr;
65292be092SMarc Marí         }
66292be092SMarc Marí     }
67292be092SMarc Marí 
68292be092SMarc Marí     QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME);
69292be092SMarc Marí     return insr;
70292be092SMarc Marí }
71292be092SMarc Marí 
72292be092SMarc Marí static inline uint64_t mlist_boundary(MemBlock *node)
73292be092SMarc Marí {
74292be092SMarc Marí     return node->size + node->addr;
75292be092SMarc Marí }
76292be092SMarc Marí 
77292be092SMarc Marí static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right)
78292be092SMarc Marí {
79292be092SMarc Marí     g_assert(head && left && right);
80292be092SMarc Marí 
81292be092SMarc Marí     left->size += right->size;
82292be092SMarc Marí     mlist_delete(head, right);
83292be092SMarc Marí     return left;
84292be092SMarc Marí }
85292be092SMarc Marí 
86292be092SMarc Marí static void mlist_coalesce(MemList *head, MemBlock *node)
87292be092SMarc Marí {
88292be092SMarc Marí     g_assert(node);
89292be092SMarc Marí     MemBlock *left;
90292be092SMarc Marí     MemBlock *right;
91292be092SMarc Marí     char merge;
92292be092SMarc Marí 
93292be092SMarc Marí     do {
94292be092SMarc Marí         merge = 0;
95eae3eb3eSPaolo Bonzini         left = QTAILQ_PREV(node, MLIST_ENTNAME);
96292be092SMarc Marí         right = QTAILQ_NEXT(node, MLIST_ENTNAME);
97292be092SMarc Marí 
98292be092SMarc Marí         /* clowns to the left of me */
99292be092SMarc Marí         if (left && mlist_boundary(left) == node->addr) {
100292be092SMarc Marí             node = mlist_join(head, left, node);
101292be092SMarc Marí             merge = 1;
102292be092SMarc Marí         }
103292be092SMarc Marí 
104292be092SMarc Marí         /* jokers to the right */
105292be092SMarc Marí         if (right && mlist_boundary(node) == right->addr) {
106292be092SMarc Marí             node = mlist_join(head, node, right);
107292be092SMarc Marí             merge = 1;
108292be092SMarc Marí         }
109292be092SMarc Marí 
110292be092SMarc Marí     } while (merge);
111292be092SMarc Marí }
112292be092SMarc Marí 
113f6f363c1SJohn Snow static MemBlock *mlist_new(uint64_t addr, uint64_t size)
114f6f363c1SJohn Snow {
115f6f363c1SJohn Snow     MemBlock *block;
116f6f363c1SJohn Snow 
117f6f363c1SJohn Snow     if (!size) {
118f6f363c1SJohn Snow         return NULL;
119f6f363c1SJohn Snow     }
120790bbb97SMarc-André Lureau     block = g_new0(MemBlock, 1);
121f6f363c1SJohn Snow 
122f6f363c1SJohn Snow     block->addr = addr;
123f6f363c1SJohn Snow     block->size = size;
124f6f363c1SJohn Snow 
125f6f363c1SJohn Snow     return block;
126f6f363c1SJohn Snow }
127f6f363c1SJohn Snow 
128292be092SMarc Marí static uint64_t mlist_fulfill(QGuestAllocator *s, MemBlock *freenode,
129292be092SMarc Marí                                                                 uint64_t size)
130292be092SMarc Marí {
131292be092SMarc Marí     uint64_t addr;
132292be092SMarc Marí     MemBlock *usednode;
133292be092SMarc Marí 
134292be092SMarc Marí     g_assert(freenode);
135292be092SMarc Marí     g_assert_cmpint(freenode->size, >=, size);
136292be092SMarc Marí 
137292be092SMarc Marí     addr = freenode->addr;
138292be092SMarc Marí     if (freenode->size == size) {
139292be092SMarc Marí         /* re-use this freenode as our used node */
140085248aeSJohn Snow         QTAILQ_REMOVE(s->free, freenode, MLIST_ENTNAME);
141292be092SMarc Marí         usednode = freenode;
142292be092SMarc Marí     } else {
143292be092SMarc Marí         /* adjust the free node and create a new used node */
144292be092SMarc Marí         freenode->addr += size;
145292be092SMarc Marí         freenode->size -= size;
146292be092SMarc Marí         usednode = mlist_new(addr, size);
147292be092SMarc Marí     }
148292be092SMarc Marí 
149085248aeSJohn Snow     mlist_sort_insert(s->used, usednode);
150292be092SMarc Marí     return addr;
151292be092SMarc Marí }
152292be092SMarc Marí 
153292be092SMarc Marí /* To assert the correctness of the list.
154292be092SMarc Marí  * Used only if ALLOC_PARANOID is set. */
155292be092SMarc Marí static void mlist_check(QGuestAllocator *s)
156292be092SMarc Marí {
157292be092SMarc Marí     MemBlock *node;
158292be092SMarc Marí     uint64_t addr = s->start > 0 ? s->start - 1 : 0;
159292be092SMarc Marí     uint64_t next = s->start;
160292be092SMarc Marí 
161085248aeSJohn Snow     QTAILQ_FOREACH(node, s->free, MLIST_ENTNAME) {
162292be092SMarc Marí         g_assert_cmpint(node->addr, >, addr);
163292be092SMarc Marí         g_assert_cmpint(node->addr, >=, next);
164292be092SMarc Marí         addr = node->addr;
165292be092SMarc Marí         next = node->addr + node->size;
166292be092SMarc Marí     }
167292be092SMarc Marí 
168292be092SMarc Marí     addr = s->start > 0 ? s->start - 1 : 0;
169292be092SMarc Marí     next = s->start;
170085248aeSJohn Snow     QTAILQ_FOREACH(node, s->used, MLIST_ENTNAME) {
171292be092SMarc Marí         g_assert_cmpint(node->addr, >, addr);
172292be092SMarc Marí         g_assert_cmpint(node->addr, >=, next);
173292be092SMarc Marí         addr = node->addr;
174292be092SMarc Marí         next = node->addr + node->size;
175292be092SMarc Marí     }
176292be092SMarc Marí }
177292be092SMarc Marí 
178292be092SMarc Marí static uint64_t mlist_alloc(QGuestAllocator *s, uint64_t size)
179292be092SMarc Marí {
180292be092SMarc Marí     MemBlock *node;
181292be092SMarc Marí 
182085248aeSJohn Snow     node = mlist_find_space(s->free, size);
183292be092SMarc Marí     if (!node) {
184292be092SMarc Marí         fprintf(stderr, "Out of guest memory.\n");
185292be092SMarc Marí         g_assert_not_reached();
186292be092SMarc Marí     }
187292be092SMarc Marí     return mlist_fulfill(s, node, size);
188292be092SMarc Marí }
189292be092SMarc Marí 
190292be092SMarc Marí static void mlist_free(QGuestAllocator *s, uint64_t addr)
191292be092SMarc Marí {
192292be092SMarc Marí     MemBlock *node;
193292be092SMarc Marí 
194292be092SMarc Marí     if (addr == 0) {
195292be092SMarc Marí         return;
196292be092SMarc Marí     }
197292be092SMarc Marí 
198085248aeSJohn Snow     node = mlist_find_key(s->used, addr);
199292be092SMarc Marí     if (!node) {
200292be092SMarc Marí         fprintf(stderr, "Error: no record found for an allocation at "
201292be092SMarc Marí                 "0x%016" PRIx64 ".\n",
202292be092SMarc Marí                 addr);
203292be092SMarc Marí         g_assert_not_reached();
204292be092SMarc Marí     }
205292be092SMarc Marí 
206292be092SMarc Marí     /* Rip it out of the used list and re-insert back into the free list. */
207085248aeSJohn Snow     QTAILQ_REMOVE(s->used, node, MLIST_ENTNAME);
208085248aeSJohn Snow     mlist_sort_insert(s->free, node);
209085248aeSJohn Snow     mlist_coalesce(s->free, node);
210292be092SMarc Marí }
211292be092SMarc Marí 
212292be092SMarc Marí /*
213292be092SMarc Marí  * Mostly for valgrind happiness, but it does offer
214292be092SMarc Marí  * a chokepoint for debugging guest memory leaks, too.
215292be092SMarc Marí  */
216*eb5937baSPaolo Bonzini void alloc_destroy(QGuestAllocator *allocator)
217292be092SMarc Marí {
218292be092SMarc Marí     MemBlock *node;
219292be092SMarc Marí     MemBlock *tmp;
220292be092SMarc Marí     QAllocOpts mask;
221292be092SMarc Marí 
222292be092SMarc Marí     /* Check for guest leaks, and destroy the list. */
223085248aeSJohn Snow     QTAILQ_FOREACH_SAFE(node, allocator->used, MLIST_ENTNAME, tmp) {
224292be092SMarc Marí         if (allocator->opts & (ALLOC_LEAK_WARN | ALLOC_LEAK_ASSERT)) {
225292be092SMarc Marí             fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; "
226292be092SMarc Marí                     "size 0x%016" PRIx64 ".\n",
227292be092SMarc Marí                     node->addr, node->size);
228292be092SMarc Marí         }
229292be092SMarc Marí         if (allocator->opts & (ALLOC_LEAK_ASSERT)) {
230292be092SMarc Marí             g_assert_not_reached();
231292be092SMarc Marí         }
232292be092SMarc Marí         g_free(node);
233292be092SMarc Marí     }
234292be092SMarc Marí 
235292be092SMarc Marí     /* If we have previously asserted that there are no leaks, then there
236292be092SMarc Marí      * should be only one node here with a specific address and size. */
237292be092SMarc Marí     mask = ALLOC_LEAK_ASSERT | ALLOC_PARANOID;
238085248aeSJohn Snow     QTAILQ_FOREACH_SAFE(node, allocator->free, MLIST_ENTNAME, tmp) {
239292be092SMarc Marí         if ((allocator->opts & mask) == mask) {
240292be092SMarc Marí             if ((node->addr != allocator->start) ||
241292be092SMarc Marí                 (node->size != allocator->end - allocator->start)) {
242292be092SMarc Marí                 fprintf(stderr, "Free list is corrupted.\n");
243292be092SMarc Marí                 g_assert_not_reached();
244292be092SMarc Marí             }
245292be092SMarc Marí         }
246292be092SMarc Marí 
247292be092SMarc Marí         g_free(node);
248292be092SMarc Marí     }
249292be092SMarc Marí 
250085248aeSJohn Snow     g_free(allocator->used);
251085248aeSJohn Snow     g_free(allocator->free);
252292be092SMarc Marí }
253292be092SMarc Marí 
254292be092SMarc Marí uint64_t guest_alloc(QGuestAllocator *allocator, size_t size)
255292be092SMarc Marí {
256292be092SMarc Marí     uint64_t rsize = size;
257292be092SMarc Marí     uint64_t naddr;
258292be092SMarc Marí 
259b1b66c3bSJohn Snow     if (!size) {
260b1b66c3bSJohn Snow         return 0;
261b1b66c3bSJohn Snow     }
262b1b66c3bSJohn Snow 
263292be092SMarc Marí     rsize += (allocator->page_size - 1);
264292be092SMarc Marí     rsize &= -allocator->page_size;
265292be092SMarc Marí     g_assert_cmpint((allocator->start + rsize), <=, allocator->end);
266292be092SMarc Marí     g_assert_cmpint(rsize, >=, size);
267292be092SMarc Marí 
268292be092SMarc Marí     naddr = mlist_alloc(allocator, rsize);
269292be092SMarc Marí     if (allocator->opts & ALLOC_PARANOID) {
270292be092SMarc Marí         mlist_check(allocator);
271292be092SMarc Marí     }
272292be092SMarc Marí 
273292be092SMarc Marí     return naddr;
274292be092SMarc Marí }
275292be092SMarc Marí 
276292be092SMarc Marí void guest_free(QGuestAllocator *allocator, uint64_t addr)
277292be092SMarc Marí {
27828452758SFam Zheng     if (!addr) {
27928452758SFam Zheng         return;
28028452758SFam Zheng     }
281292be092SMarc Marí     mlist_free(allocator, addr);
282292be092SMarc Marí     if (allocator->opts & ALLOC_PARANOID) {
283292be092SMarc Marí         mlist_check(allocator);
284292be092SMarc Marí     }
285292be092SMarc Marí }
286af77f2cdSJohn Snow 
287*eb5937baSPaolo Bonzini void alloc_init(QGuestAllocator *s, QAllocOpts opts,
288*eb5937baSPaolo Bonzini                 uint64_t start, uint64_t end,
289*eb5937baSPaolo Bonzini                 size_t page_size)
290af77f2cdSJohn Snow {
291af77f2cdSJohn Snow     MemBlock *node;
292af77f2cdSJohn Snow 
293*eb5937baSPaolo Bonzini     s->opts = opts;
294af77f2cdSJohn Snow     s->start = start;
295af77f2cdSJohn Snow     s->end = end;
296af77f2cdSJohn Snow 
297790bbb97SMarc-André Lureau     s->used = g_new(MemList, 1);
298790bbb97SMarc-André Lureau     s->free = g_new(MemList, 1);
299085248aeSJohn Snow     QTAILQ_INIT(s->used);
300085248aeSJohn Snow     QTAILQ_INIT(s->free);
301af77f2cdSJohn Snow 
302af77f2cdSJohn Snow     node = mlist_new(s->start, s->end - s->start);
303085248aeSJohn Snow     QTAILQ_INSERT_HEAD(s->free, node, MLIST_ENTNAME);
304af77f2cdSJohn Snow 
305*eb5937baSPaolo Bonzini     s->page_size = page_size;
306f6f363c1SJohn Snow }
307259342d3SJohn Snow 
308259342d3SJohn Snow void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts)
309259342d3SJohn Snow {
310259342d3SJohn Snow     allocator->opts |= opts;
311259342d3SJohn Snow }
312085248aeSJohn Snow 
313085248aeSJohn Snow void migrate_allocator(QGuestAllocator *src,
314085248aeSJohn Snow                        QGuestAllocator *dst)
315085248aeSJohn Snow {
316085248aeSJohn Snow     MemBlock *node, *tmp;
317085248aeSJohn Snow     MemList *tmpused, *tmpfree;
318085248aeSJohn Snow 
319085248aeSJohn Snow     /* The general memory layout should be equivalent,
320085248aeSJohn Snow      * though opts can differ. */
321085248aeSJohn Snow     g_assert_cmphex(src->start, ==, dst->start);
322085248aeSJohn Snow     g_assert_cmphex(src->end, ==, dst->end);
323085248aeSJohn Snow 
324085248aeSJohn Snow     /* Destroy (silently, regardless of options) the dest-list: */
325085248aeSJohn Snow     QTAILQ_FOREACH_SAFE(node, dst->used, MLIST_ENTNAME, tmp) {
326085248aeSJohn Snow         g_free(node);
327085248aeSJohn Snow     }
328085248aeSJohn Snow     QTAILQ_FOREACH_SAFE(node, dst->free, MLIST_ENTNAME, tmp) {
329085248aeSJohn Snow         g_free(node);
330085248aeSJohn Snow     }
331085248aeSJohn Snow 
332085248aeSJohn Snow     tmpused = dst->used;
333085248aeSJohn Snow     tmpfree = dst->free;
334085248aeSJohn Snow 
335085248aeSJohn Snow     /* Inherit the lists of the source allocator: */
336085248aeSJohn Snow     dst->used = src->used;
337085248aeSJohn Snow     dst->free = src->free;
338085248aeSJohn Snow 
339085248aeSJohn Snow     /* Source is now re-initialized, the source memory is 'invalid' now: */
340085248aeSJohn Snow     src->used = tmpused;
341085248aeSJohn Snow     src->free = tmpfree;
342085248aeSJohn Snow     QTAILQ_INIT(src->used);
343085248aeSJohn Snow     QTAILQ_INIT(src->free);
344085248aeSJohn Snow     node = mlist_new(src->start, src->end - src->start);
345085248aeSJohn Snow     QTAILQ_INSERT_HEAD(src->free, node, MLIST_ENTNAME);
346085248aeSJohn Snow     return;
347085248aeSJohn Snow }
348