xref: /openbmc/qemu/tests/unit/test-coroutine.c (revision f09cc2b50c41842da521a14fe6d245ee0fa84a6a)
1da668aa1SThomas Huth /*
2da668aa1SThomas Huth  * Coroutine tests
3da668aa1SThomas Huth  *
4da668aa1SThomas Huth  * Copyright IBM, Corp. 2011
5da668aa1SThomas Huth  *
6da668aa1SThomas Huth  * Authors:
7da668aa1SThomas Huth  *  Stefan Hajnoczi    <stefanha@linux.vnet.ibm.com>
8da668aa1SThomas Huth  *
9da668aa1SThomas Huth  * This work is licensed under the terms of the GNU LGPL, version 2 or later.
10da668aa1SThomas Huth  * See the COPYING.LIB file in the top-level directory.
11da668aa1SThomas Huth  *
12da668aa1SThomas Huth  */
13da668aa1SThomas Huth 
14da668aa1SThomas Huth #include "qemu/osdep.h"
15da668aa1SThomas Huth #include "qemu/coroutine_int.h"
16da668aa1SThomas Huth 
17da668aa1SThomas Huth /*
18da668aa1SThomas Huth  * Check that qemu_in_coroutine() works
19da668aa1SThomas Huth  */
20da668aa1SThomas Huth 
verify_in_coroutine(void * opaque)21da668aa1SThomas Huth static void coroutine_fn verify_in_coroutine(void *opaque)
22da668aa1SThomas Huth {
23da668aa1SThomas Huth     g_assert(qemu_in_coroutine());
24da668aa1SThomas Huth }
25da668aa1SThomas Huth 
test_in_coroutine(void)26da668aa1SThomas Huth static void test_in_coroutine(void)
27da668aa1SThomas Huth {
28da668aa1SThomas Huth     Coroutine *coroutine;
29da668aa1SThomas Huth 
30da668aa1SThomas Huth     g_assert(!qemu_in_coroutine());
31da668aa1SThomas Huth 
32da668aa1SThomas Huth     coroutine = qemu_coroutine_create(verify_in_coroutine, NULL);
33da668aa1SThomas Huth     qemu_coroutine_enter(coroutine);
34da668aa1SThomas Huth }
35da668aa1SThomas Huth 
36da668aa1SThomas Huth /*
37da668aa1SThomas Huth  * Check that qemu_coroutine_self() works
38da668aa1SThomas Huth  */
39da668aa1SThomas Huth 
verify_self(void * opaque)40da668aa1SThomas Huth static void coroutine_fn verify_self(void *opaque)
41da668aa1SThomas Huth {
42da668aa1SThomas Huth     Coroutine **p_co = opaque;
43da668aa1SThomas Huth     g_assert(qemu_coroutine_self() == *p_co);
44da668aa1SThomas Huth }
45da668aa1SThomas Huth 
test_self(void)46da668aa1SThomas Huth static void test_self(void)
47da668aa1SThomas Huth {
48da668aa1SThomas Huth     Coroutine *coroutine;
49da668aa1SThomas Huth 
50da668aa1SThomas Huth     coroutine = qemu_coroutine_create(verify_self, &coroutine);
51da668aa1SThomas Huth     qemu_coroutine_enter(coroutine);
52da668aa1SThomas Huth }
53da668aa1SThomas Huth 
54da668aa1SThomas Huth /*
55da668aa1SThomas Huth  * Check that qemu_coroutine_entered() works
56da668aa1SThomas Huth  */
57da668aa1SThomas Huth 
verify_entered_step_2(void * opaque)58da668aa1SThomas Huth static void coroutine_fn verify_entered_step_2(void *opaque)
59da668aa1SThomas Huth {
60da668aa1SThomas Huth     Coroutine *caller = (Coroutine *)opaque;
61da668aa1SThomas Huth 
62da668aa1SThomas Huth     g_assert(qemu_coroutine_entered(caller));
63da668aa1SThomas Huth     g_assert(qemu_coroutine_entered(qemu_coroutine_self()));
64da668aa1SThomas Huth     qemu_coroutine_yield();
65da668aa1SThomas Huth 
66da668aa1SThomas Huth     /* Once more to check it still works after yielding */
67da668aa1SThomas Huth     g_assert(qemu_coroutine_entered(caller));
68da668aa1SThomas Huth     g_assert(qemu_coroutine_entered(qemu_coroutine_self()));
69da668aa1SThomas Huth }
70da668aa1SThomas Huth 
verify_entered_step_1(void * opaque)71da668aa1SThomas Huth static void coroutine_fn verify_entered_step_1(void *opaque)
72da668aa1SThomas Huth {
73da668aa1SThomas Huth     Coroutine *self = qemu_coroutine_self();
74da668aa1SThomas Huth     Coroutine *coroutine;
75da668aa1SThomas Huth 
76da668aa1SThomas Huth     g_assert(qemu_coroutine_entered(self));
77da668aa1SThomas Huth 
78da668aa1SThomas Huth     coroutine = qemu_coroutine_create(verify_entered_step_2, self);
79da668aa1SThomas Huth     g_assert(!qemu_coroutine_entered(coroutine));
80da668aa1SThomas Huth     qemu_coroutine_enter(coroutine);
81da668aa1SThomas Huth     g_assert(!qemu_coroutine_entered(coroutine));
82da668aa1SThomas Huth     qemu_coroutine_enter(coroutine);
83da668aa1SThomas Huth }
84da668aa1SThomas Huth 
test_entered(void)85da668aa1SThomas Huth static void test_entered(void)
86da668aa1SThomas Huth {
87da668aa1SThomas Huth     Coroutine *coroutine;
88da668aa1SThomas Huth 
89da668aa1SThomas Huth     coroutine = qemu_coroutine_create(verify_entered_step_1, NULL);
90da668aa1SThomas Huth     g_assert(!qemu_coroutine_entered(coroutine));
91da668aa1SThomas Huth     qemu_coroutine_enter(coroutine);
92da668aa1SThomas Huth }
93da668aa1SThomas Huth 
94da668aa1SThomas Huth /*
95da668aa1SThomas Huth  * Check that coroutines may nest multiple levels
96da668aa1SThomas Huth  */
97da668aa1SThomas Huth 
98da668aa1SThomas Huth typedef struct {
99da668aa1SThomas Huth     unsigned int n_enter;   /* num coroutines entered */
100da668aa1SThomas Huth     unsigned int n_return;  /* num coroutines returned */
101da668aa1SThomas Huth     unsigned int max;       /* maximum level of nesting */
102da668aa1SThomas Huth } NestData;
103da668aa1SThomas Huth 
nest(void * opaque)104da668aa1SThomas Huth static void coroutine_fn nest(void *opaque)
105da668aa1SThomas Huth {
106da668aa1SThomas Huth     NestData *nd = opaque;
107da668aa1SThomas Huth 
108da668aa1SThomas Huth     nd->n_enter++;
109da668aa1SThomas Huth 
110da668aa1SThomas Huth     if (nd->n_enter < nd->max) {
111da668aa1SThomas Huth         Coroutine *child;
112da668aa1SThomas Huth 
113da668aa1SThomas Huth         child = qemu_coroutine_create(nest, nd);
114da668aa1SThomas Huth         qemu_coroutine_enter(child);
115da668aa1SThomas Huth     }
116da668aa1SThomas Huth 
117da668aa1SThomas Huth     nd->n_return++;
118da668aa1SThomas Huth }
119da668aa1SThomas Huth 
test_nesting(void)120da668aa1SThomas Huth static void test_nesting(void)
121da668aa1SThomas Huth {
122da668aa1SThomas Huth     Coroutine *root;
123da668aa1SThomas Huth     NestData nd = {
124da668aa1SThomas Huth         .n_enter  = 0,
125da668aa1SThomas Huth         .n_return = 0,
126da668aa1SThomas Huth         .max      = 128,
127da668aa1SThomas Huth     };
128da668aa1SThomas Huth 
129da668aa1SThomas Huth     root = qemu_coroutine_create(nest, &nd);
130da668aa1SThomas Huth     qemu_coroutine_enter(root);
131da668aa1SThomas Huth 
132da668aa1SThomas Huth     /* Must enter and return from max nesting level */
133da668aa1SThomas Huth     g_assert_cmpint(nd.n_enter, ==, nd.max);
134da668aa1SThomas Huth     g_assert_cmpint(nd.n_return, ==, nd.max);
135da668aa1SThomas Huth }
136da668aa1SThomas Huth 
137da668aa1SThomas Huth /*
138da668aa1SThomas Huth  * Check that yield/enter transfer control correctly
139da668aa1SThomas Huth  */
140da668aa1SThomas Huth 
yield_5_times(void * opaque)141da668aa1SThomas Huth static void coroutine_fn yield_5_times(void *opaque)
142da668aa1SThomas Huth {
143da668aa1SThomas Huth     bool *done = opaque;
144da668aa1SThomas Huth     int i;
145da668aa1SThomas Huth 
146da668aa1SThomas Huth     for (i = 0; i < 5; i++) {
147da668aa1SThomas Huth         qemu_coroutine_yield();
148da668aa1SThomas Huth     }
149da668aa1SThomas Huth     *done = true;
150da668aa1SThomas Huth }
151da668aa1SThomas Huth 
test_yield(void)152da668aa1SThomas Huth static void test_yield(void)
153da668aa1SThomas Huth {
154da668aa1SThomas Huth     Coroutine *coroutine;
155da668aa1SThomas Huth     bool done = false;
156da668aa1SThomas Huth     int i = -1; /* one extra time to return from coroutine */
157da668aa1SThomas Huth 
158da668aa1SThomas Huth     coroutine = qemu_coroutine_create(yield_5_times, &done);
159da668aa1SThomas Huth     while (!done) {
160da668aa1SThomas Huth         qemu_coroutine_enter(coroutine);
161da668aa1SThomas Huth         i++;
162da668aa1SThomas Huth     }
163da668aa1SThomas Huth     g_assert_cmpint(i, ==, 5); /* coroutine must yield 5 times */
164da668aa1SThomas Huth }
165da668aa1SThomas Huth 
c2_fn(void * opaque)166da668aa1SThomas Huth static void coroutine_fn c2_fn(void *opaque)
167da668aa1SThomas Huth {
168da668aa1SThomas Huth     qemu_coroutine_yield();
169da668aa1SThomas Huth }
170da668aa1SThomas Huth 
c1_fn(void * opaque)171da668aa1SThomas Huth static void coroutine_fn c1_fn(void *opaque)
172da668aa1SThomas Huth {
173da668aa1SThomas Huth     Coroutine *c2 = opaque;
174da668aa1SThomas Huth     qemu_coroutine_enter(c2);
175da668aa1SThomas Huth }
176da668aa1SThomas Huth 
test_no_dangling_access(void)177da668aa1SThomas Huth static void test_no_dangling_access(void)
178da668aa1SThomas Huth {
179da668aa1SThomas Huth     Coroutine *c1;
180da668aa1SThomas Huth     Coroutine *c2;
181da668aa1SThomas Huth     Coroutine tmp;
182da668aa1SThomas Huth 
183da668aa1SThomas Huth     c2 = qemu_coroutine_create(c2_fn, NULL);
184da668aa1SThomas Huth     c1 = qemu_coroutine_create(c1_fn, c2);
185da668aa1SThomas Huth 
186da668aa1SThomas Huth     qemu_coroutine_enter(c1);
187da668aa1SThomas Huth 
188da668aa1SThomas Huth     /* c1 shouldn't be used any more now; make sure we segfault if it is */
189da668aa1SThomas Huth     tmp = *c1;
190da668aa1SThomas Huth     memset(c1, 0xff, sizeof(Coroutine));
191da668aa1SThomas Huth     qemu_coroutine_enter(c2);
192da668aa1SThomas Huth 
193da668aa1SThomas Huth     /* Must restore the coroutine now to avoid corrupted pool */
194da668aa1SThomas Huth     *c1 = tmp;
195da668aa1SThomas Huth }
196da668aa1SThomas Huth 
197da668aa1SThomas Huth static bool locked;
198*89c90405SPhilippe Mathieu-Daudé static int done_count;
199da668aa1SThomas Huth 
mutex_fn(void * opaque)200da668aa1SThomas Huth static void coroutine_fn mutex_fn(void *opaque)
201da668aa1SThomas Huth {
202da668aa1SThomas Huth     CoMutex *m = opaque;
203da668aa1SThomas Huth     qemu_co_mutex_lock(m);
204da668aa1SThomas Huth     assert(!locked);
205da668aa1SThomas Huth     locked = true;
206da668aa1SThomas Huth     qemu_coroutine_yield();
207da668aa1SThomas Huth     locked = false;
208da668aa1SThomas Huth     qemu_co_mutex_unlock(m);
209*89c90405SPhilippe Mathieu-Daudé     done_count++;
210da668aa1SThomas Huth }
211da668aa1SThomas Huth 
lockable_fn(void * opaque)212da668aa1SThomas Huth static void coroutine_fn lockable_fn(void *opaque)
213da668aa1SThomas Huth {
214da668aa1SThomas Huth     QemuLockable *x = opaque;
215da668aa1SThomas Huth     qemu_lockable_lock(x);
216da668aa1SThomas Huth     assert(!locked);
217da668aa1SThomas Huth     locked = true;
218da668aa1SThomas Huth     qemu_coroutine_yield();
219da668aa1SThomas Huth     locked = false;
220da668aa1SThomas Huth     qemu_lockable_unlock(x);
221*89c90405SPhilippe Mathieu-Daudé     done_count++;
222da668aa1SThomas Huth }
223da668aa1SThomas Huth 
do_test_co_mutex(CoroutineEntry * entry,void * opaque)224da668aa1SThomas Huth static void do_test_co_mutex(CoroutineEntry *entry, void *opaque)
225da668aa1SThomas Huth {
226da668aa1SThomas Huth     Coroutine *c1 = qemu_coroutine_create(entry, opaque);
227da668aa1SThomas Huth     Coroutine *c2 = qemu_coroutine_create(entry, opaque);
228da668aa1SThomas Huth 
229*89c90405SPhilippe Mathieu-Daudé     done_count = 0;
230da668aa1SThomas Huth     qemu_coroutine_enter(c1);
231da668aa1SThomas Huth     g_assert(locked);
232da668aa1SThomas Huth     qemu_coroutine_enter(c2);
233da668aa1SThomas Huth 
234da668aa1SThomas Huth     /* Unlock queues c2.  It is then started automatically when c1 yields or
235da668aa1SThomas Huth      * terminates.
236da668aa1SThomas Huth      */
237da668aa1SThomas Huth     qemu_coroutine_enter(c1);
238*89c90405SPhilippe Mathieu-Daudé     g_assert_cmpint(done_count, ==, 1);
239da668aa1SThomas Huth     g_assert(locked);
240da668aa1SThomas Huth 
241da668aa1SThomas Huth     qemu_coroutine_enter(c2);
242*89c90405SPhilippe Mathieu-Daudé     g_assert_cmpint(done_count, ==, 2);
243da668aa1SThomas Huth     g_assert(!locked);
244da668aa1SThomas Huth }
245da668aa1SThomas Huth 
test_co_mutex(void)246da668aa1SThomas Huth static void test_co_mutex(void)
247da668aa1SThomas Huth {
248da668aa1SThomas Huth     CoMutex m;
249da668aa1SThomas Huth 
250da668aa1SThomas Huth     qemu_co_mutex_init(&m);
251da668aa1SThomas Huth     do_test_co_mutex(mutex_fn, &m);
252da668aa1SThomas Huth }
253da668aa1SThomas Huth 
test_co_mutex_lockable(void)254da668aa1SThomas Huth static void test_co_mutex_lockable(void)
255da668aa1SThomas Huth {
256da668aa1SThomas Huth     CoMutex m;
257da668aa1SThomas Huth     CoMutex *null_pointer = NULL;
258da668aa1SThomas Huth 
259da668aa1SThomas Huth     qemu_co_mutex_init(&m);
260da668aa1SThomas Huth     do_test_co_mutex(lockable_fn, QEMU_MAKE_LOCKABLE(&m));
261da668aa1SThomas Huth 
262da668aa1SThomas Huth     g_assert(QEMU_MAKE_LOCKABLE(null_pointer) == NULL);
263da668aa1SThomas Huth }
264da668aa1SThomas Huth 
26525bc2daeSPaolo Bonzini static CoRwlock rwlock;
26625bc2daeSPaolo Bonzini 
26725bc2daeSPaolo Bonzini /* Test that readers are properly sent back to the queue when upgrading,
26825bc2daeSPaolo Bonzini  * even if they are the sole readers.  The test scenario is as follows:
26925bc2daeSPaolo Bonzini  *
27025bc2daeSPaolo Bonzini  *
27125bc2daeSPaolo Bonzini  * | c1           | c2         |
27225bc2daeSPaolo Bonzini  * |--------------+------------+
27325bc2daeSPaolo Bonzini  * | rdlock       |            |
27425bc2daeSPaolo Bonzini  * | yield        |            |
27525bc2daeSPaolo Bonzini  * |              | wrlock     |
27625bc2daeSPaolo Bonzini  * |              | <queued>   |
27725bc2daeSPaolo Bonzini  * | upgrade      |            |
27825bc2daeSPaolo Bonzini  * | <queued>     | <dequeued> |
27925bc2daeSPaolo Bonzini  * |              | unlock     |
28025bc2daeSPaolo Bonzini  * | <dequeued>   |            |
28125bc2daeSPaolo Bonzini  * | unlock       |            |
28225bc2daeSPaolo Bonzini  */
28325bc2daeSPaolo Bonzini 
rwlock_yield_upgrade(void * opaque)28425bc2daeSPaolo Bonzini static void coroutine_fn rwlock_yield_upgrade(void *opaque)
28525bc2daeSPaolo Bonzini {
28625bc2daeSPaolo Bonzini     qemu_co_rwlock_rdlock(&rwlock);
28725bc2daeSPaolo Bonzini     qemu_coroutine_yield();
28825bc2daeSPaolo Bonzini 
28925bc2daeSPaolo Bonzini     qemu_co_rwlock_upgrade(&rwlock);
29025bc2daeSPaolo Bonzini     qemu_co_rwlock_unlock(&rwlock);
29125bc2daeSPaolo Bonzini 
29225bc2daeSPaolo Bonzini     *(bool *)opaque = true;
29325bc2daeSPaolo Bonzini }
29425bc2daeSPaolo Bonzini 
rwlock_wrlock_yield(void * opaque)29525bc2daeSPaolo Bonzini static void coroutine_fn rwlock_wrlock_yield(void *opaque)
29625bc2daeSPaolo Bonzini {
29725bc2daeSPaolo Bonzini     qemu_co_rwlock_wrlock(&rwlock);
29825bc2daeSPaolo Bonzini     qemu_coroutine_yield();
29925bc2daeSPaolo Bonzini 
30025bc2daeSPaolo Bonzini     qemu_co_rwlock_unlock(&rwlock);
30125bc2daeSPaolo Bonzini     *(bool *)opaque = true;
30225bc2daeSPaolo Bonzini }
30325bc2daeSPaolo Bonzini 
test_co_rwlock_upgrade(void)30425bc2daeSPaolo Bonzini static void test_co_rwlock_upgrade(void)
30525bc2daeSPaolo Bonzini {
30625bc2daeSPaolo Bonzini     bool c1_done = false;
30725bc2daeSPaolo Bonzini     bool c2_done = false;
30825bc2daeSPaolo Bonzini     Coroutine *c1, *c2;
30925bc2daeSPaolo Bonzini 
31025bc2daeSPaolo Bonzini     qemu_co_rwlock_init(&rwlock);
31125bc2daeSPaolo Bonzini     c1 = qemu_coroutine_create(rwlock_yield_upgrade, &c1_done);
31225bc2daeSPaolo Bonzini     c2 = qemu_coroutine_create(rwlock_wrlock_yield, &c2_done);
31325bc2daeSPaolo Bonzini 
31425bc2daeSPaolo Bonzini     qemu_coroutine_enter(c1);
31525bc2daeSPaolo Bonzini     qemu_coroutine_enter(c2);
31625bc2daeSPaolo Bonzini 
31725bc2daeSPaolo Bonzini     /* c1 now should go to sleep.  */
31825bc2daeSPaolo Bonzini     qemu_coroutine_enter(c1);
31925bc2daeSPaolo Bonzini     g_assert(!c1_done);
32025bc2daeSPaolo Bonzini 
32125bc2daeSPaolo Bonzini     qemu_coroutine_enter(c2);
32225bc2daeSPaolo Bonzini     g_assert(c1_done);
32325bc2daeSPaolo Bonzini     g_assert(c2_done);
32425bc2daeSPaolo Bonzini }
32525bc2daeSPaolo Bonzini 
rwlock_rdlock_yield(void * opaque)326b6489ac0SDavid Edmondson static void coroutine_fn rwlock_rdlock_yield(void *opaque)
327b6489ac0SDavid Edmondson {
328b6489ac0SDavid Edmondson     qemu_co_rwlock_rdlock(&rwlock);
329b6489ac0SDavid Edmondson     qemu_coroutine_yield();
330b6489ac0SDavid Edmondson 
331b6489ac0SDavid Edmondson     qemu_co_rwlock_unlock(&rwlock);
332b6489ac0SDavid Edmondson     qemu_coroutine_yield();
333b6489ac0SDavid Edmondson 
334b6489ac0SDavid Edmondson     *(bool *)opaque = true;
335b6489ac0SDavid Edmondson }
336b6489ac0SDavid Edmondson 
rwlock_wrlock_downgrade(void * opaque)337b6489ac0SDavid Edmondson static void coroutine_fn rwlock_wrlock_downgrade(void *opaque)
338b6489ac0SDavid Edmondson {
339b6489ac0SDavid Edmondson     qemu_co_rwlock_wrlock(&rwlock);
340b6489ac0SDavid Edmondson 
341b6489ac0SDavid Edmondson     qemu_co_rwlock_downgrade(&rwlock);
342b6489ac0SDavid Edmondson     qemu_co_rwlock_unlock(&rwlock);
343b6489ac0SDavid Edmondson     *(bool *)opaque = true;
344b6489ac0SDavid Edmondson }
345b6489ac0SDavid Edmondson 
rwlock_rdlock(void * opaque)346b6489ac0SDavid Edmondson static void coroutine_fn rwlock_rdlock(void *opaque)
347b6489ac0SDavid Edmondson {
348b6489ac0SDavid Edmondson     qemu_co_rwlock_rdlock(&rwlock);
349b6489ac0SDavid Edmondson 
350b6489ac0SDavid Edmondson     qemu_co_rwlock_unlock(&rwlock);
351b6489ac0SDavid Edmondson     *(bool *)opaque = true;
352b6489ac0SDavid Edmondson }
353b6489ac0SDavid Edmondson 
rwlock_wrlock(void * opaque)354b6489ac0SDavid Edmondson static void coroutine_fn rwlock_wrlock(void *opaque)
355b6489ac0SDavid Edmondson {
356b6489ac0SDavid Edmondson     qemu_co_rwlock_wrlock(&rwlock);
357b6489ac0SDavid Edmondson 
358b6489ac0SDavid Edmondson     qemu_co_rwlock_unlock(&rwlock);
359b6489ac0SDavid Edmondson     *(bool *)opaque = true;
360b6489ac0SDavid Edmondson }
361b6489ac0SDavid Edmondson 
362b6489ac0SDavid Edmondson /*
363b6489ac0SDavid Edmondson  * Check that downgrading a reader-writer lock does not cause a hang.
364b6489ac0SDavid Edmondson  *
365b6489ac0SDavid Edmondson  * Four coroutines are used to produce a situation where there are
366b6489ac0SDavid Edmondson  * both reader and writer hopefuls waiting to acquire an rwlock that
367b6489ac0SDavid Edmondson  * is held by a reader.
368b6489ac0SDavid Edmondson  *
369b6489ac0SDavid Edmondson  * The correct sequence of operations we aim to provoke can be
370b6489ac0SDavid Edmondson  * represented as:
371b6489ac0SDavid Edmondson  *
372b6489ac0SDavid Edmondson  * | c1     | c2         | c3         | c4         |
373b6489ac0SDavid Edmondson  * |--------+------------+------------+------------|
374b6489ac0SDavid Edmondson  * | rdlock |            |            |            |
375b6489ac0SDavid Edmondson  * | yield  |            |            |            |
376b6489ac0SDavid Edmondson  * |        | wrlock     |            |            |
377b6489ac0SDavid Edmondson  * |        | <queued>   |            |            |
378b6489ac0SDavid Edmondson  * |        |            | rdlock     |            |
379b6489ac0SDavid Edmondson  * |        |            | <queued>   |            |
380b6489ac0SDavid Edmondson  * |        |            |            | wrlock     |
381b6489ac0SDavid Edmondson  * |        |            |            | <queued>   |
382b6489ac0SDavid Edmondson  * | unlock |            |            |            |
383b6489ac0SDavid Edmondson  * | yield  |            |            |            |
384b6489ac0SDavid Edmondson  * |        | <dequeued> |            |            |
385b6489ac0SDavid Edmondson  * |        | downgrade  |            |            |
386b6489ac0SDavid Edmondson  * |        |            | <dequeued> |            |
387b6489ac0SDavid Edmondson  * |        |            | unlock     |            |
388b6489ac0SDavid Edmondson  * |        | ...        |            |            |
389b6489ac0SDavid Edmondson  * |        | unlock     |            |            |
390b6489ac0SDavid Edmondson  * |        |            |            | <dequeued> |
391b6489ac0SDavid Edmondson  * |        |            |            | unlock     |
392b6489ac0SDavid Edmondson  */
test_co_rwlock_downgrade(void)393b6489ac0SDavid Edmondson static void test_co_rwlock_downgrade(void)
394b6489ac0SDavid Edmondson {
395b6489ac0SDavid Edmondson     bool c1_done = false;
396b6489ac0SDavid Edmondson     bool c2_done = false;
397b6489ac0SDavid Edmondson     bool c3_done = false;
398b6489ac0SDavid Edmondson     bool c4_done = false;
399b6489ac0SDavid Edmondson     Coroutine *c1, *c2, *c3, *c4;
400b6489ac0SDavid Edmondson 
401b6489ac0SDavid Edmondson     qemu_co_rwlock_init(&rwlock);
402b6489ac0SDavid Edmondson 
403b6489ac0SDavid Edmondson     c1 = qemu_coroutine_create(rwlock_rdlock_yield, &c1_done);
404b6489ac0SDavid Edmondson     c2 = qemu_coroutine_create(rwlock_wrlock_downgrade, &c2_done);
405b6489ac0SDavid Edmondson     c3 = qemu_coroutine_create(rwlock_rdlock, &c3_done);
406b6489ac0SDavid Edmondson     c4 = qemu_coroutine_create(rwlock_wrlock, &c4_done);
407b6489ac0SDavid Edmondson 
408b6489ac0SDavid Edmondson     qemu_coroutine_enter(c1);
409b6489ac0SDavid Edmondson     qemu_coroutine_enter(c2);
410b6489ac0SDavid Edmondson     qemu_coroutine_enter(c3);
411b6489ac0SDavid Edmondson     qemu_coroutine_enter(c4);
412b6489ac0SDavid Edmondson 
413b6489ac0SDavid Edmondson     qemu_coroutine_enter(c1);
414b6489ac0SDavid Edmondson 
415b6489ac0SDavid Edmondson     g_assert(c2_done);
416b6489ac0SDavid Edmondson     g_assert(c3_done);
417b6489ac0SDavid Edmondson     g_assert(c4_done);
418b6489ac0SDavid Edmondson 
419b6489ac0SDavid Edmondson     qemu_coroutine_enter(c1);
420b6489ac0SDavid Edmondson 
421b6489ac0SDavid Edmondson     g_assert(c1_done);
422b6489ac0SDavid Edmondson }
423b6489ac0SDavid Edmondson 
424da668aa1SThomas Huth /*
425da668aa1SThomas Huth  * Check that creation, enter, and return work
426da668aa1SThomas Huth  */
427da668aa1SThomas Huth 
set_and_exit(void * opaque)428da668aa1SThomas Huth static void coroutine_fn set_and_exit(void *opaque)
429da668aa1SThomas Huth {
430da668aa1SThomas Huth     bool *done = opaque;
431da668aa1SThomas Huth 
432da668aa1SThomas Huth     *done = true;
433da668aa1SThomas Huth }
434da668aa1SThomas Huth 
test_lifecycle(void)435da668aa1SThomas Huth static void test_lifecycle(void)
436da668aa1SThomas Huth {
437da668aa1SThomas Huth     Coroutine *coroutine;
438da668aa1SThomas Huth     bool done = false;
439da668aa1SThomas Huth 
440da668aa1SThomas Huth     /* Create, enter, and return from coroutine */
441da668aa1SThomas Huth     coroutine = qemu_coroutine_create(set_and_exit, &done);
442da668aa1SThomas Huth     qemu_coroutine_enter(coroutine);
443da668aa1SThomas Huth     g_assert(done); /* expect done to be true (first time) */
444da668aa1SThomas Huth 
445da668aa1SThomas Huth     /* Repeat to check that no state affects this test */
446da668aa1SThomas Huth     done = false;
447da668aa1SThomas Huth     coroutine = qemu_coroutine_create(set_and_exit, &done);
448da668aa1SThomas Huth     qemu_coroutine_enter(coroutine);
449da668aa1SThomas Huth     g_assert(done); /* expect done to be true (second time) */
450da668aa1SThomas Huth }
451da668aa1SThomas Huth 
452da668aa1SThomas Huth 
453da668aa1SThomas Huth #define RECORD_SIZE 10 /* Leave some room for expansion */
454da668aa1SThomas Huth struct coroutine_position {
455da668aa1SThomas Huth     int func;
456da668aa1SThomas Huth     int state;
457da668aa1SThomas Huth };
458da668aa1SThomas Huth static struct coroutine_position records[RECORD_SIZE];
459da668aa1SThomas Huth static unsigned record_pos;
460da668aa1SThomas Huth 
record_push(int func,int state)461da668aa1SThomas Huth static void record_push(int func, int state)
462da668aa1SThomas Huth {
463da668aa1SThomas Huth     struct coroutine_position *cp = &records[record_pos++];
464da668aa1SThomas Huth     g_assert_cmpint(record_pos, <, RECORD_SIZE);
465da668aa1SThomas Huth     cp->func = func;
466da668aa1SThomas Huth     cp->state = state;
467da668aa1SThomas Huth }
468da668aa1SThomas Huth 
co_order_test(void * opaque)469da668aa1SThomas Huth static void coroutine_fn co_order_test(void *opaque)
470da668aa1SThomas Huth {
471da668aa1SThomas Huth     record_push(2, 1);
472da668aa1SThomas Huth     g_assert(qemu_in_coroutine());
473da668aa1SThomas Huth     qemu_coroutine_yield();
474da668aa1SThomas Huth     record_push(2, 2);
475da668aa1SThomas Huth     g_assert(qemu_in_coroutine());
476da668aa1SThomas Huth }
477da668aa1SThomas Huth 
do_order_test(void)478da668aa1SThomas Huth static void do_order_test(void)
479da668aa1SThomas Huth {
480da668aa1SThomas Huth     Coroutine *co;
481da668aa1SThomas Huth 
482da668aa1SThomas Huth     co = qemu_coroutine_create(co_order_test, NULL);
483da668aa1SThomas Huth     record_push(1, 1);
484da668aa1SThomas Huth     qemu_coroutine_enter(co);
485da668aa1SThomas Huth     record_push(1, 2);
486da668aa1SThomas Huth     g_assert(!qemu_in_coroutine());
487da668aa1SThomas Huth     qemu_coroutine_enter(co);
488da668aa1SThomas Huth     record_push(1, 3);
489da668aa1SThomas Huth     g_assert(!qemu_in_coroutine());
490da668aa1SThomas Huth }
491da668aa1SThomas Huth 
test_order(void)492da668aa1SThomas Huth static void test_order(void)
493da668aa1SThomas Huth {
494da668aa1SThomas Huth     int i;
495da668aa1SThomas Huth     const struct coroutine_position expected_pos[] = {
496da668aa1SThomas Huth         {1, 1,}, {2, 1}, {1, 2}, {2, 2}, {1, 3}
497da668aa1SThomas Huth     };
498da668aa1SThomas Huth     do_order_test();
499da668aa1SThomas Huth     g_assert_cmpint(record_pos, ==, 5);
500da668aa1SThomas Huth     for (i = 0; i < record_pos; i++) {
501da668aa1SThomas Huth         g_assert_cmpint(records[i].func , ==, expected_pos[i].func );
502da668aa1SThomas Huth         g_assert_cmpint(records[i].state, ==, expected_pos[i].state);
503da668aa1SThomas Huth     }
504da668aa1SThomas Huth }
505da668aa1SThomas Huth /*
506da668aa1SThomas Huth  * Lifecycle benchmark
507da668aa1SThomas Huth  */
508da668aa1SThomas Huth 
empty_coroutine(void * opaque)509da668aa1SThomas Huth static void coroutine_fn empty_coroutine(void *opaque)
510da668aa1SThomas Huth {
511da668aa1SThomas Huth     /* Do nothing */
512da668aa1SThomas Huth }
513da668aa1SThomas Huth 
perf_lifecycle(void)514da668aa1SThomas Huth static void perf_lifecycle(void)
515da668aa1SThomas Huth {
516da668aa1SThomas Huth     Coroutine *coroutine;
517da668aa1SThomas Huth     unsigned int i, max;
518da668aa1SThomas Huth     double duration;
519da668aa1SThomas Huth 
520da668aa1SThomas Huth     max = 1000000;
521da668aa1SThomas Huth 
522da668aa1SThomas Huth     g_test_timer_start();
523da668aa1SThomas Huth     for (i = 0; i < max; i++) {
524da668aa1SThomas Huth         coroutine = qemu_coroutine_create(empty_coroutine, NULL);
525da668aa1SThomas Huth         qemu_coroutine_enter(coroutine);
526da668aa1SThomas Huth     }
527da668aa1SThomas Huth     duration = g_test_timer_elapsed();
528da668aa1SThomas Huth 
529da668aa1SThomas Huth     g_test_message("Lifecycle %u iterations: %f s", max, duration);
530da668aa1SThomas Huth }
531da668aa1SThomas Huth 
perf_nesting(void)532da668aa1SThomas Huth static void perf_nesting(void)
533da668aa1SThomas Huth {
534da668aa1SThomas Huth     unsigned int i, maxcycles, maxnesting;
535da668aa1SThomas Huth     double duration;
536da668aa1SThomas Huth 
537da668aa1SThomas Huth     maxcycles = 10000;
538da668aa1SThomas Huth     maxnesting = 1000;
539da668aa1SThomas Huth     Coroutine *root;
540da668aa1SThomas Huth 
541da668aa1SThomas Huth     g_test_timer_start();
542da668aa1SThomas Huth     for (i = 0; i < maxcycles; i++) {
543da668aa1SThomas Huth         NestData nd = {
544da668aa1SThomas Huth             .n_enter  = 0,
545da668aa1SThomas Huth             .n_return = 0,
546da668aa1SThomas Huth             .max      = maxnesting,
547da668aa1SThomas Huth         };
548da668aa1SThomas Huth         root = qemu_coroutine_create(nest, &nd);
549da668aa1SThomas Huth         qemu_coroutine_enter(root);
550da668aa1SThomas Huth     }
551da668aa1SThomas Huth     duration = g_test_timer_elapsed();
552da668aa1SThomas Huth 
553da668aa1SThomas Huth     g_test_message("Nesting %u iterations of %u depth each: %f s",
554da668aa1SThomas Huth         maxcycles, maxnesting, duration);
555da668aa1SThomas Huth }
556da668aa1SThomas Huth 
557da668aa1SThomas Huth /*
558da668aa1SThomas Huth  * Yield benchmark
559da668aa1SThomas Huth  */
560da668aa1SThomas Huth 
yield_loop(void * opaque)561da668aa1SThomas Huth static void coroutine_fn yield_loop(void *opaque)
562da668aa1SThomas Huth {
563da668aa1SThomas Huth     unsigned int *counter = opaque;
564da668aa1SThomas Huth 
565da668aa1SThomas Huth     while ((*counter) > 0) {
566da668aa1SThomas Huth         (*counter)--;
567da668aa1SThomas Huth         qemu_coroutine_yield();
568da668aa1SThomas Huth     }
569da668aa1SThomas Huth }
570da668aa1SThomas Huth 
perf_yield(void)571da668aa1SThomas Huth static void perf_yield(void)
572da668aa1SThomas Huth {
573da668aa1SThomas Huth     unsigned int i, maxcycles;
574da668aa1SThomas Huth     double duration;
575da668aa1SThomas Huth 
576da668aa1SThomas Huth     maxcycles = 100000000;
577da668aa1SThomas Huth     i = maxcycles;
578da668aa1SThomas Huth     Coroutine *coroutine = qemu_coroutine_create(yield_loop, &i);
579da668aa1SThomas Huth 
580da668aa1SThomas Huth     g_test_timer_start();
581da668aa1SThomas Huth     while (i > 0) {
582da668aa1SThomas Huth         qemu_coroutine_enter(coroutine);
583da668aa1SThomas Huth     }
584da668aa1SThomas Huth     duration = g_test_timer_elapsed();
585da668aa1SThomas Huth 
586da668aa1SThomas Huth     g_test_message("Yield %u iterations: %f s", maxcycles, duration);
587da668aa1SThomas Huth }
588da668aa1SThomas Huth 
dummy(unsigned * i)589da668aa1SThomas Huth static __attribute__((noinline)) void dummy(unsigned *i)
590da668aa1SThomas Huth {
591da668aa1SThomas Huth     (*i)--;
592da668aa1SThomas Huth }
593da668aa1SThomas Huth 
perf_baseline(void)594da668aa1SThomas Huth static void perf_baseline(void)
595da668aa1SThomas Huth {
596da668aa1SThomas Huth     unsigned int i, maxcycles;
597da668aa1SThomas Huth     double duration;
598da668aa1SThomas Huth 
599da668aa1SThomas Huth     maxcycles = 100000000;
600da668aa1SThomas Huth     i = maxcycles;
601da668aa1SThomas Huth 
602da668aa1SThomas Huth     g_test_timer_start();
603da668aa1SThomas Huth     while (i > 0) {
604da668aa1SThomas Huth         dummy(&i);
605da668aa1SThomas Huth     }
606da668aa1SThomas Huth     duration = g_test_timer_elapsed();
607da668aa1SThomas Huth 
608da668aa1SThomas Huth     g_test_message("Function call %u iterations: %f s", maxcycles, duration);
609da668aa1SThomas Huth }
610da668aa1SThomas Huth 
perf_cost_func(void * opaque)611f7bbb156SMarc-André Lureau static __attribute__((noinline)) void coroutine_fn perf_cost_func(void *opaque)
612da668aa1SThomas Huth {
613da668aa1SThomas Huth     qemu_coroutine_yield();
614da668aa1SThomas Huth }
615da668aa1SThomas Huth 
perf_cost(void)616da668aa1SThomas Huth static void perf_cost(void)
617da668aa1SThomas Huth {
618da668aa1SThomas Huth     const unsigned long maxcycles = 40000000;
619da668aa1SThomas Huth     unsigned long i = 0;
620da668aa1SThomas Huth     double duration;
621da668aa1SThomas Huth     unsigned long ops;
622da668aa1SThomas Huth     Coroutine *co;
623da668aa1SThomas Huth 
624da668aa1SThomas Huth     g_test_timer_start();
625da668aa1SThomas Huth     while (i++ < maxcycles) {
626da668aa1SThomas Huth         co = qemu_coroutine_create(perf_cost_func, &i);
627da668aa1SThomas Huth         qemu_coroutine_enter(co);
628da668aa1SThomas Huth         qemu_coroutine_enter(co);
629da668aa1SThomas Huth     }
630da668aa1SThomas Huth     duration = g_test_timer_elapsed();
631da668aa1SThomas Huth     ops = (long)(maxcycles / (duration * 1000));
632da668aa1SThomas Huth 
633da668aa1SThomas Huth     g_test_message("Run operation %lu iterations %f s, %luK operations/s, "
634da668aa1SThomas Huth                    "%luns per coroutine",
635da668aa1SThomas Huth                    maxcycles,
636da668aa1SThomas Huth                    duration, ops,
637da668aa1SThomas Huth                    (unsigned long)(1000000000.0 * duration / maxcycles));
638da668aa1SThomas Huth }
639da668aa1SThomas Huth 
main(int argc,char ** argv)640da668aa1SThomas Huth int main(int argc, char **argv)
641da668aa1SThomas Huth {
642da668aa1SThomas Huth     g_test_init(&argc, &argv, NULL);
643da668aa1SThomas Huth 
644da668aa1SThomas Huth     /* This test assumes there is a freelist and marks freed coroutine memory
645da668aa1SThomas Huth      * with a sentinel value.  If there is no freelist this would legitimately
646da668aa1SThomas Huth      * crash, so skip it.
647da668aa1SThomas Huth      */
648230f6e06SPaolo Bonzini     if (IS_ENABLED(CONFIG_COROUTINE_POOL)) {
649da668aa1SThomas Huth         g_test_add_func("/basic/no-dangling-access", test_no_dangling_access);
650da668aa1SThomas Huth     }
651da668aa1SThomas Huth 
652da668aa1SThomas Huth     g_test_add_func("/basic/lifecycle", test_lifecycle);
653da668aa1SThomas Huth     g_test_add_func("/basic/yield", test_yield);
654da668aa1SThomas Huth     g_test_add_func("/basic/nesting", test_nesting);
655da668aa1SThomas Huth     g_test_add_func("/basic/self", test_self);
656da668aa1SThomas Huth     g_test_add_func("/basic/entered", test_entered);
657da668aa1SThomas Huth     g_test_add_func("/basic/in_coroutine", test_in_coroutine);
658da668aa1SThomas Huth     g_test_add_func("/basic/order", test_order);
659da668aa1SThomas Huth     g_test_add_func("/locking/co-mutex", test_co_mutex);
660da668aa1SThomas Huth     g_test_add_func("/locking/co-mutex/lockable", test_co_mutex_lockable);
66125bc2daeSPaolo Bonzini     g_test_add_func("/locking/co-rwlock/upgrade", test_co_rwlock_upgrade);
662b6489ac0SDavid Edmondson     g_test_add_func("/locking/co-rwlock/downgrade", test_co_rwlock_downgrade);
663da668aa1SThomas Huth     if (g_test_perf()) {
664da668aa1SThomas Huth         g_test_add_func("/perf/lifecycle", perf_lifecycle);
665da668aa1SThomas Huth         g_test_add_func("/perf/nesting", perf_nesting);
666da668aa1SThomas Huth         g_test_add_func("/perf/yield", perf_yield);
667da668aa1SThomas Huth         g_test_add_func("/perf/function-call", perf_baseline);
668da668aa1SThomas Huth         g_test_add_func("/perf/cost", perf_cost);
669da668aa1SThomas Huth     }
670da668aa1SThomas Huth     return g_test_run();
671da668aa1SThomas Huth }
672