mirror of
https://gitlab.com/qemu-project/qemu
synced 2024-09-06 09:55:55 +00:00
![Peter Maydell](/assets/img/avatar_default.png)
A gcc codegen bug in x86_64-w64-mingw32-gcc (GCC) 4.6.3 means that non-debug builds of QEMU for Windows tend to assert when using coroutines. Work around this by marking qemu_coroutine_switch as noinline. If we allow gcc to inline qemu_coroutine_switch into coroutine_trampoline, then it hoists the code to get the address of the TLS variable "current" out of the while() loop. This is an invalid transformation because the SwitchToFiber() call may be called when running thread A but return in thread B, and so we might be in a different thread context each time round the loop. This can happen quite often. Typically. a coroutine is started when a VCPU thread does bdrv_aio_readv: VCPU thread main VCPU thread coroutine I/O coroutine bdrv_aio_readv -----> start I/O operation thread_pool_submit_co <------------ yields back to emulation Then I/O finishes and the thread-pool.c event notifier triggers in the I/O thread. event_notifier_ready calls thread_pool_co_cb, and the I/O coroutine now restarts *in another thread*: iothread main iothread coroutine I/O coroutine (formerly in VCPU thread) event_notifier_ready thread_pool_co_cb -----> current = I/O coroutine; call AIO callback But on Win32, because of the bug, the "current" being set here the current coroutine of the VCPU thread, not the iothread. noinline is a good-enough workaround, and quite unlikely to break in the future. (Thanks to Paolo Bonzini for assistance in diagnosing the problem and providing the detailed example/ascii art quoted above.) Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 1403535303-14939-1-git-send-email-peter.maydell@linaro.org Reviewed-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Richard Henderson <rth@twiddle.net>
102 lines
3 KiB
C
102 lines
3 KiB
C
/*
|
|
* Win32 coroutine initialization code
|
|
*
|
|
* Copyright (c) 2011 Kevin Wolf <kwolf@redhat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "qemu-common.h"
|
|
#include "block/coroutine_int.h"
|
|
|
|
typedef struct
|
|
{
|
|
Coroutine base;
|
|
|
|
LPVOID fiber;
|
|
CoroutineAction action;
|
|
} CoroutineWin32;
|
|
|
|
static __thread CoroutineWin32 leader;
|
|
static __thread Coroutine *current;
|
|
|
|
/* This function is marked noinline to prevent GCC from inlining it
|
|
* into coroutine_trampoline(). If we allow it to do that then it
|
|
* hoists the code to get the address of the TLS variable "current"
|
|
* out of the while() loop. This is an invalid transformation because
|
|
* the SwitchToFiber() call may be called when running thread A but
|
|
* return in thread B, and so we might be in a different thread
|
|
* context each time round the loop.
|
|
*/
|
|
CoroutineAction __attribute__((noinline))
|
|
qemu_coroutine_switch(Coroutine *from_, Coroutine *to_,
|
|
CoroutineAction action)
|
|
{
|
|
CoroutineWin32 *from = DO_UPCAST(CoroutineWin32, base, from_);
|
|
CoroutineWin32 *to = DO_UPCAST(CoroutineWin32, base, to_);
|
|
|
|
current = to_;
|
|
|
|
to->action = action;
|
|
SwitchToFiber(to->fiber);
|
|
return from->action;
|
|
}
|
|
|
|
static void CALLBACK coroutine_trampoline(void *co_)
|
|
{
|
|
Coroutine *co = co_;
|
|
|
|
while (true) {
|
|
co->entry(co->entry_arg);
|
|
qemu_coroutine_switch(co, co->caller, COROUTINE_TERMINATE);
|
|
}
|
|
}
|
|
|
|
Coroutine *qemu_coroutine_new(void)
|
|
{
|
|
const size_t stack_size = 1 << 20;
|
|
CoroutineWin32 *co;
|
|
|
|
co = g_malloc0(sizeof(*co));
|
|
co->fiber = CreateFiber(stack_size, coroutine_trampoline, &co->base);
|
|
return &co->base;
|
|
}
|
|
|
|
void qemu_coroutine_delete(Coroutine *co_)
|
|
{
|
|
CoroutineWin32 *co = DO_UPCAST(CoroutineWin32, base, co_);
|
|
|
|
DeleteFiber(co->fiber);
|
|
g_free(co);
|
|
}
|
|
|
|
Coroutine *qemu_coroutine_self(void)
|
|
{
|
|
if (!current) {
|
|
current = &leader.base;
|
|
leader.fiber = ConvertThreadToFiber(NULL);
|
|
}
|
|
return current;
|
|
}
|
|
|
|
bool qemu_in_coroutine(void)
|
|
{
|
|
return current && current->caller;
|
|
}
|