xref: /qemu/tests/qtest/libqos/libqos-malloc.c (revision 8a2b516ba2855c4530388051de2b8d17bc780ea8)
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"
14*b243c73cSXuzhou Cheng #include "libqos-malloc.h"
1587776ab7SPaolo Bonzini #include "qemu/host-utils.h"
16292be092SMarc Marí 
17f6f363c1SJohn Snow typedef struct MemBlock {
18f6f363c1SJohn Snow     QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME;
19f6f363c1SJohn Snow     uint64_t size;
20f6f363c1SJohn Snow     uint64_t addr;
21f6f363c1SJohn Snow } MemBlock;
22f6f363c1SJohn Snow 
23f6f363c1SJohn Snow #define DEFAULT_PAGE_SIZE 4096
24f6f363c1SJohn Snow 
mlist_delete(MemList * list,MemBlock * node)25292be092SMarc Marí static void mlist_delete(MemList *list, MemBlock *node)
26292be092SMarc Marí {
27292be092SMarc Marí     g_assert(list && node);
28292be092SMarc Marí     QTAILQ_REMOVE(list, node, MLIST_ENTNAME);
29292be092SMarc Marí     g_free(node);
30292be092SMarc Marí }
31292be092SMarc Marí 
mlist_find_key(MemList * head,uint64_t addr)32292be092SMarc Marí static MemBlock *mlist_find_key(MemList *head, uint64_t addr)
33292be092SMarc Marí {
34292be092SMarc Marí     MemBlock *node;
35292be092SMarc Marí     QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
36292be092SMarc Marí         if (node->addr == addr) {
37292be092SMarc Marí             return node;
38292be092SMarc Marí         }
39292be092SMarc Marí     }
40292be092SMarc Marí     return NULL;
41292be092SMarc Marí }
42292be092SMarc Marí 
mlist_find_space(MemList * head,uint64_t size)43292be092SMarc Marí static MemBlock *mlist_find_space(MemList *head, uint64_t size)
44292be092SMarc Marí {
45292be092SMarc Marí     MemBlock *node;
46292be092SMarc Marí 
47292be092SMarc Marí     QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
48292be092SMarc Marí         if (node->size >= size) {
49292be092SMarc Marí             return node;
50292be092SMarc Marí         }
51292be092SMarc Marí     }
52292be092SMarc Marí     return NULL;
53292be092SMarc Marí }
54292be092SMarc Marí 
mlist_sort_insert(MemList * head,MemBlock * insr)55292be092SMarc Marí static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr)
56292be092SMarc Marí {
57292be092SMarc Marí     MemBlock *node;
58292be092SMarc Marí     g_assert(head && insr);
59292be092SMarc Marí 
60292be092SMarc Marí     QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
61292be092SMarc Marí         if (insr->addr < node->addr) {
62292be092SMarc Marí             QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME);
63292be092SMarc Marí             return insr;
64292be092SMarc Marí         }
65292be092SMarc Marí     }
66292be092SMarc Marí 
67292be092SMarc Marí     QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME);
68292be092SMarc Marí     return insr;
69292be092SMarc Marí }
70292be092SMarc Marí 
mlist_boundary(MemBlock * node)71292be092SMarc Marí static inline uint64_t mlist_boundary(MemBlock *node)
72292be092SMarc Marí {
73292be092SMarc Marí     return node->size + node->addr;
74292be092SMarc Marí }
75292be092SMarc Marí 
mlist_join(MemList * head,MemBlock * left,MemBlock * right)76292be092SMarc Marí static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right)
77292be092SMarc Marí {
78292be092SMarc Marí     g_assert(head && left && right);
79292be092SMarc Marí 
80292be092SMarc Marí     left->size += right->size;
81292be092SMarc Marí     mlist_delete(head, right);
82292be092SMarc Marí     return left;
83292be092SMarc Marí }
84292be092SMarc Marí 
mlist_coalesce(MemList * head,MemBlock * node)85292be092SMarc Marí static void mlist_coalesce(MemList *head, MemBlock *node)
86292be092SMarc Marí {
87292be092SMarc Marí     g_assert(node);
88292be092SMarc Marí     MemBlock *left;
89292be092SMarc Marí     MemBlock *right;
90292be092SMarc Marí     char merge;
91292be092SMarc Marí 
92292be092SMarc Marí     do {
93292be092SMarc Marí         merge = 0;
94eae3eb3eSPaolo Bonzini         left = QTAILQ_PREV(node, MLIST_ENTNAME);
95292be092SMarc Marí         right = QTAILQ_NEXT(node, MLIST_ENTNAME);
96292be092SMarc Marí 
97292be092SMarc Marí         /* clowns to the left of me */
98292be092SMarc Marí         if (left && mlist_boundary(left) == node->addr) {
99292be092SMarc Marí             node = mlist_join(head, left, node);
100292be092SMarc Marí             merge = 1;
101292be092SMarc Marí         }
102292be092SMarc Marí 
103292be092SMarc Marí         /* jokers to the right */
104292be092SMarc Marí         if (right && mlist_boundary(node) == right->addr) {
105292be092SMarc Marí             node = mlist_join(head, node, right);
106292be092SMarc Marí             merge = 1;
107292be092SMarc Marí         }
108292be092SMarc Marí 
109292be092SMarc Marí     } while (merge);
110292be092SMarc Marí }
111292be092SMarc Marí 
mlist_new(uint64_t addr,uint64_t size)112f6f363c1SJohn Snow static MemBlock *mlist_new(uint64_t addr, uint64_t size)
113f6f363c1SJohn Snow {
114f6f363c1SJohn Snow     MemBlock *block;
115f6f363c1SJohn Snow 
116f6f363c1SJohn Snow     if (!size) {
117f6f363c1SJohn Snow         return NULL;
118f6f363c1SJohn Snow     }
119790bbb97SMarc-André Lureau     block = g_new0(MemBlock, 1);
120f6f363c1SJohn Snow 
121f6f363c1SJohn Snow     block->addr = addr;
122f6f363c1SJohn Snow     block->size = size;
123f6f363c1SJohn Snow 
124f6f363c1SJohn Snow     return block;
125f6f363c1SJohn Snow }
126f6f363c1SJohn Snow 
mlist_fulfill(QGuestAllocator * s,MemBlock * freenode,uint64_t size)127292be092SMarc Marí static uint64_t mlist_fulfill(QGuestAllocator *s, MemBlock *freenode,
128292be092SMarc Marí                                                                 uint64_t size)
129292be092SMarc Marí {
130292be092SMarc Marí     uint64_t addr;
131292be092SMarc Marí     MemBlock *usednode;
132292be092SMarc Marí 
133292be092SMarc Marí     g_assert(freenode);
134292be092SMarc Marí     g_assert_cmpint(freenode->size, >=, size);
135292be092SMarc Marí 
136292be092SMarc Marí     addr = freenode->addr;
137292be092SMarc Marí     if (freenode->size == size) {
138292be092SMarc Marí         /* re-use this freenode as our used node */
139085248aeSJohn Snow         QTAILQ_REMOVE(s->free, freenode, MLIST_ENTNAME);
140292be092SMarc Marí         usednode = freenode;
141292be092SMarc Marí     } else {
142292be092SMarc Marí         /* adjust the free node and create a new used node */
143292be092SMarc Marí         freenode->addr += size;
144292be092SMarc Marí         freenode->size -= size;
145292be092SMarc Marí         usednode = mlist_new(addr, size);
146292be092SMarc Marí     }
147292be092SMarc Marí 
148085248aeSJohn Snow     mlist_sort_insert(s->used, usednode);
149292be092SMarc Marí     return addr;
150292be092SMarc Marí }
151292be092SMarc Marí 
152292be092SMarc Marí /* To assert the correctness of the list.
153292be092SMarc Marí  * Used only if ALLOC_PARANOID is set. */
mlist_check(QGuestAllocator * s)154292be092SMarc Marí static void mlist_check(QGuestAllocator *s)
155292be092SMarc Marí {
156292be092SMarc Marí     MemBlock *node;
157292be092SMarc Marí     uint64_t addr = s->start > 0 ? s->start - 1 : 0;
158292be092SMarc Marí     uint64_t next = s->start;
159292be092SMarc Marí 
160085248aeSJohn Snow     QTAILQ_FOREACH(node, s->free, MLIST_ENTNAME) {
161292be092SMarc Marí         g_assert_cmpint(node->addr, >, addr);
162292be092SMarc Marí         g_assert_cmpint(node->addr, >=, next);
163292be092SMarc Marí         addr = node->addr;
164292be092SMarc Marí         next = node->addr + node->size;
165292be092SMarc Marí     }
166292be092SMarc Marí 
167292be092SMarc Marí     addr = s->start > 0 ? s->start - 1 : 0;
168292be092SMarc Marí     next = s->start;
169085248aeSJohn Snow     QTAILQ_FOREACH(node, s->used, MLIST_ENTNAME) {
170292be092SMarc Marí         g_assert_cmpint(node->addr, >, addr);
171292be092SMarc Marí         g_assert_cmpint(node->addr, >=, next);
172292be092SMarc Marí         addr = node->addr;
173292be092SMarc Marí         next = node->addr + node->size;
174292be092SMarc Marí     }
175292be092SMarc Marí }
176292be092SMarc Marí 
mlist_alloc(QGuestAllocator * s,uint64_t size)177292be092SMarc Marí static uint64_t mlist_alloc(QGuestAllocator *s, uint64_t size)
178292be092SMarc Marí {
179292be092SMarc Marí     MemBlock *node;
180292be092SMarc Marí 
181085248aeSJohn Snow     node = mlist_find_space(s->free, size);
182292be092SMarc Marí     if (!node) {
183292be092SMarc Marí         fprintf(stderr, "Out of guest memory.\n");
184292be092SMarc Marí         g_assert_not_reached();
185292be092SMarc Marí     }
186292be092SMarc Marí     return mlist_fulfill(s, node, size);
187292be092SMarc Marí }
188292be092SMarc Marí 
mlist_free(QGuestAllocator * s,uint64_t addr)189292be092SMarc Marí static void mlist_free(QGuestAllocator *s, uint64_t addr)
190292be092SMarc Marí {
191292be092SMarc Marí     MemBlock *node;
192292be092SMarc Marí 
193292be092SMarc Marí     if (addr == 0) {
194292be092SMarc Marí         return;
195292be092SMarc Marí     }
196292be092SMarc Marí 
197085248aeSJohn Snow     node = mlist_find_key(s->used, addr);
198292be092SMarc Marí     if (!node) {
199292be092SMarc Marí         fprintf(stderr, "Error: no record found for an allocation at "
200292be092SMarc Marí                 "0x%016" PRIx64 ".\n",
201292be092SMarc Marí                 addr);
202292be092SMarc Marí         g_assert_not_reached();
203292be092SMarc Marí     }
204292be092SMarc Marí 
205292be092SMarc Marí     /* Rip it out of the used list and re-insert back into the free list. */
206085248aeSJohn Snow     QTAILQ_REMOVE(s->used, node, MLIST_ENTNAME);
207085248aeSJohn Snow     mlist_sort_insert(s->free, node);
208085248aeSJohn Snow     mlist_coalesce(s->free, node);
209292be092SMarc Marí }
210292be092SMarc Marí 
211292be092SMarc Marí /*
212292be092SMarc Marí  * Mostly for valgrind happiness, but it does offer
213292be092SMarc Marí  * a chokepoint for debugging guest memory leaks, too.
214292be092SMarc Marí  */
alloc_destroy(QGuestAllocator * allocator)215eb5937baSPaolo Bonzini void alloc_destroy(QGuestAllocator *allocator)
216292be092SMarc Marí {
217292be092SMarc Marí     MemBlock *node;
218292be092SMarc Marí     MemBlock *tmp;
219292be092SMarc Marí     QAllocOpts mask;
220292be092SMarc Marí 
221292be092SMarc Marí     /* Check for guest leaks, and destroy the list. */
222085248aeSJohn Snow     QTAILQ_FOREACH_SAFE(node, allocator->used, MLIST_ENTNAME, tmp) {
223292be092SMarc Marí         if (allocator->opts & (ALLOC_LEAK_WARN | ALLOC_LEAK_ASSERT)) {
224292be092SMarc Marí             fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; "
225292be092SMarc Marí                     "size 0x%016" PRIx64 ".\n",
226292be092SMarc Marí                     node->addr, node->size);
227292be092SMarc Marí         }
228292be092SMarc Marí         if (allocator->opts & (ALLOC_LEAK_ASSERT)) {
229292be092SMarc Marí             g_assert_not_reached();
230292be092SMarc Marí         }
231292be092SMarc Marí         g_free(node);
232292be092SMarc Marí     }
233292be092SMarc Marí 
234292be092SMarc Marí     /* If we have previously asserted that there are no leaks, then there
235292be092SMarc Marí      * should be only one node here with a specific address and size. */
236292be092SMarc Marí     mask = ALLOC_LEAK_ASSERT | ALLOC_PARANOID;
237085248aeSJohn Snow     QTAILQ_FOREACH_SAFE(node, allocator->free, MLIST_ENTNAME, tmp) {
238292be092SMarc Marí         if ((allocator->opts & mask) == mask) {
239292be092SMarc Marí             if ((node->addr != allocator->start) ||
240292be092SMarc Marí                 (node->size != allocator->end - allocator->start)) {
241292be092SMarc Marí                 fprintf(stderr, "Free list is corrupted.\n");
242292be092SMarc Marí                 g_assert_not_reached();
243292be092SMarc Marí             }
244292be092SMarc Marí         }
245292be092SMarc Marí 
246292be092SMarc Marí         g_free(node);
247292be092SMarc Marí     }
248292be092SMarc Marí 
249085248aeSJohn Snow     g_free(allocator->used);
250085248aeSJohn Snow     g_free(allocator->free);
251292be092SMarc Marí }
252292be092SMarc Marí 
guest_alloc(QGuestAllocator * allocator,size_t size)253292be092SMarc Marí uint64_t guest_alloc(QGuestAllocator *allocator, size_t size)
254292be092SMarc Marí {
255292be092SMarc Marí     uint64_t rsize = size;
256292be092SMarc Marí     uint64_t naddr;
257292be092SMarc Marí 
258b1b66c3bSJohn Snow     if (!size) {
259b1b66c3bSJohn Snow         return 0;
260b1b66c3bSJohn Snow     }
261b1b66c3bSJohn Snow 
262292be092SMarc Marí     rsize += (allocator->page_size - 1);
263292be092SMarc Marí     rsize &= -allocator->page_size;
264292be092SMarc Marí     g_assert_cmpint((allocator->start + rsize), <=, allocator->end);
265292be092SMarc Marí     g_assert_cmpint(rsize, >=, size);
266292be092SMarc Marí 
267292be092SMarc Marí     naddr = mlist_alloc(allocator, rsize);
268292be092SMarc Marí     if (allocator->opts & ALLOC_PARANOID) {
269292be092SMarc Marí         mlist_check(allocator);
270292be092SMarc Marí     }
271292be092SMarc Marí 
272292be092SMarc Marí     return naddr;
273292be092SMarc Marí }
274292be092SMarc Marí 
guest_free(QGuestAllocator * allocator,uint64_t addr)275292be092SMarc Marí void guest_free(QGuestAllocator *allocator, uint64_t addr)
276292be092SMarc Marí {
27728452758SFam Zheng     if (!addr) {
27828452758SFam Zheng         return;
27928452758SFam Zheng     }
280292be092SMarc Marí     mlist_free(allocator, addr);
281292be092SMarc Marí     if (allocator->opts & ALLOC_PARANOID) {
282292be092SMarc Marí         mlist_check(allocator);
283292be092SMarc Marí     }
284292be092SMarc Marí }
285af77f2cdSJohn Snow 
alloc_init(QGuestAllocator * s,QAllocOpts opts,uint64_t start,uint64_t end,size_t page_size)286eb5937baSPaolo Bonzini void alloc_init(QGuestAllocator *s, QAllocOpts opts,
287eb5937baSPaolo Bonzini                 uint64_t start, uint64_t end,
288eb5937baSPaolo Bonzini                 size_t page_size)
289af77f2cdSJohn Snow {
290af77f2cdSJohn Snow     MemBlock *node;
291af77f2cdSJohn Snow 
292eb5937baSPaolo Bonzini     s->opts = opts;
293af77f2cdSJohn Snow     s->start = start;
294af77f2cdSJohn Snow     s->end = end;
295af77f2cdSJohn Snow 
296790bbb97SMarc-André Lureau     s->used = g_new(MemList, 1);
297790bbb97SMarc-André Lureau     s->free = g_new(MemList, 1);
298085248aeSJohn Snow     QTAILQ_INIT(s->used);
299085248aeSJohn Snow     QTAILQ_INIT(s->free);
300af77f2cdSJohn Snow 
301af77f2cdSJohn Snow     node = mlist_new(s->start, s->end - s->start);
302085248aeSJohn Snow     QTAILQ_INSERT_HEAD(s->free, node, MLIST_ENTNAME);
303af77f2cdSJohn Snow 
304eb5937baSPaolo Bonzini     s->page_size = page_size;
305f6f363c1SJohn Snow }
306259342d3SJohn Snow 
alloc_set_flags(QGuestAllocator * allocator,QAllocOpts opts)307259342d3SJohn Snow void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts)
308259342d3SJohn Snow {
309259342d3SJohn Snow     allocator->opts |= opts;
310259342d3SJohn Snow }
311085248aeSJohn Snow 
migrate_allocator(QGuestAllocator * src,QGuestAllocator * dst)312085248aeSJohn Snow void migrate_allocator(QGuestAllocator *src,
313085248aeSJohn Snow                        QGuestAllocator *dst)
314085248aeSJohn Snow {
315085248aeSJohn Snow     MemBlock *node, *tmp;
316085248aeSJohn Snow     MemList *tmpused, *tmpfree;
317085248aeSJohn Snow 
318085248aeSJohn Snow     /* The general memory layout should be equivalent,
319085248aeSJohn Snow      * though opts can differ. */
320085248aeSJohn Snow     g_assert_cmphex(src->start, ==, dst->start);
321085248aeSJohn Snow     g_assert_cmphex(src->end, ==, dst->end);
322085248aeSJohn Snow 
323085248aeSJohn Snow     /* Destroy (silently, regardless of options) the dest-list: */
324085248aeSJohn Snow     QTAILQ_FOREACH_SAFE(node, dst->used, MLIST_ENTNAME, tmp) {
325085248aeSJohn Snow         g_free(node);
326085248aeSJohn Snow     }
327085248aeSJohn Snow     QTAILQ_FOREACH_SAFE(node, dst->free, MLIST_ENTNAME, tmp) {
328085248aeSJohn Snow         g_free(node);
329085248aeSJohn Snow     }
330085248aeSJohn Snow 
331085248aeSJohn Snow     tmpused = dst->used;
332085248aeSJohn Snow     tmpfree = dst->free;
333085248aeSJohn Snow 
334085248aeSJohn Snow     /* Inherit the lists of the source allocator: */
335085248aeSJohn Snow     dst->used = src->used;
336085248aeSJohn Snow     dst->free = src->free;
337085248aeSJohn Snow 
338085248aeSJohn Snow     /* Source is now re-initialized, the source memory is 'invalid' now: */
339085248aeSJohn Snow     src->used = tmpused;
340085248aeSJohn Snow     src->free = tmpfree;
341085248aeSJohn Snow     QTAILQ_INIT(src->used);
342085248aeSJohn Snow     QTAILQ_INIT(src->free);
343085248aeSJohn Snow     node = mlist_new(src->start, src->end - src->start);
344085248aeSJohn Snow     QTAILQ_INSERT_HEAD(src->free, node, MLIST_ENTNAME);
345085248aeSJohn Snow }
346