replay: rewrite async event handling
This patch decouples checkpoints and async events. It was a tricky part of replay implementation. Now it becomes much simpler and easier to maintain. Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru> Acked-by: Richard Henderson <richard.henderson@linaro.org> Message-Id: <165364837856.688121.8785039478408995979.stgit@pasha-ThinkPad-X280> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
75bbe5e5ec
commit
60618e2d77
@ -84,8 +84,7 @@ void icount_handle_deadline(void)
|
|||||||
* Don't interrupt cpu thread, when these events are waiting
|
* Don't interrupt cpu thread, when these events are waiting
|
||||||
* (i.e., there is no checkpoint)
|
* (i.e., there is no checkpoint)
|
||||||
*/
|
*/
|
||||||
if (deadline == 0
|
if (deadline == 0) {
|
||||||
&& (replay_mode != REPLAY_MODE_PLAY || replay_has_checkpoint())) {
|
|
||||||
icount_notify_aio_contexts();
|
icount_notify_aio_contexts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +108,7 @@ void icount_prepare_for_run(CPUState *cpu)
|
|||||||
|
|
||||||
replay_mutex_lock();
|
replay_mutex_lock();
|
||||||
|
|
||||||
if (cpu->icount_budget == 0 && replay_has_checkpoint()) {
|
if (cpu->icount_budget == 0) {
|
||||||
icount_notify_aio_contexts();
|
icount_notify_aio_contexts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -366,11 +366,9 @@ Here is the list of events that are written into the log:
|
|||||||
Argument: 4-byte number of executed instructions.
|
Argument: 4-byte number of executed instructions.
|
||||||
- EVENT_INTERRUPT. Used to synchronize interrupt processing.
|
- EVENT_INTERRUPT. Used to synchronize interrupt processing.
|
||||||
- EVENT_EXCEPTION. Used to synchronize exception handling.
|
- EVENT_EXCEPTION. Used to synchronize exception handling.
|
||||||
- EVENT_ASYNC. This is a group of events. They are always processed
|
- EVENT_ASYNC. This is a group of events. When such an event is generated,
|
||||||
together with checkpoints. When such an event is generated, it is
|
it is stored in the queue and processed in icount_account_warp_timer().
|
||||||
stored in the queue and processed only when checkpoint occurs.
|
Every such event has it's own id from the following list:
|
||||||
Every such event is followed by 1-byte checkpoint id and 1-byte
|
|
||||||
async event id from the following list:
|
|
||||||
- REPLAY_ASYNC_EVENT_BH. Bottom-half callback. This event synchronizes
|
- REPLAY_ASYNC_EVENT_BH. Bottom-half callback. This event synchronizes
|
||||||
callbacks that affect virtual machine state, but normally called
|
callbacks that affect virtual machine state, but normally called
|
||||||
asynchronously.
|
asynchronously.
|
||||||
@ -405,6 +403,5 @@ Here is the list of events that are written into the log:
|
|||||||
- EVENT_CLOCK + clock_id. Group of events for host clock read operations.
|
- EVENT_CLOCK + clock_id. Group of events for host clock read operations.
|
||||||
Argument: 8-byte clock value.
|
Argument: 8-byte clock value.
|
||||||
- EVENT_CHECKPOINT + checkpoint_id. Checkpoint for synchronization of
|
- EVENT_CHECKPOINT + checkpoint_id. Checkpoint for synchronization of
|
||||||
CPU, internal threads, and asynchronous input events. May be followed
|
CPU, internal threads, and asynchronous input events.
|
||||||
by one or more EVENT_ASYNC events.
|
|
||||||
- EVENT_END. Last event in the log.
|
- EVENT_END. Last event in the log.
|
||||||
|
@ -160,9 +160,14 @@ void replay_shutdown_request(ShutdownCause cause);
|
|||||||
Returns 0 in PLAY mode if checkpoint was not found.
|
Returns 0 in PLAY mode if checkpoint was not found.
|
||||||
Returns 1 in all other cases. */
|
Returns 1 in all other cases. */
|
||||||
bool replay_checkpoint(ReplayCheckpoint checkpoint);
|
bool replay_checkpoint(ReplayCheckpoint checkpoint);
|
||||||
/*! Used to determine that checkpoint is pending.
|
/*! Used to determine that checkpoint or async event is pending.
|
||||||
Does not proceed to the next event in the log. */
|
Does not proceed to the next event in the log. */
|
||||||
bool replay_has_checkpoint(void);
|
bool replay_has_event(void);
|
||||||
|
/*
|
||||||
|
* Processes the async events added to the queue (while recording)
|
||||||
|
* or reads the events from the file (while replaying).
|
||||||
|
*/
|
||||||
|
void replay_async_events(void);
|
||||||
|
|
||||||
/* Asynchronous events queue */
|
/* Asynchronous events queue */
|
||||||
|
|
||||||
|
@ -170,12 +170,11 @@ void replay_block_event(QEMUBH *bh, uint64_t id)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void replay_save_event(Event *event, int checkpoint)
|
static void replay_save_event(Event *event)
|
||||||
{
|
{
|
||||||
if (replay_mode != REPLAY_MODE_PLAY) {
|
if (replay_mode != REPLAY_MODE_PLAY) {
|
||||||
/* put the event into the file */
|
/* put the event into the file */
|
||||||
replay_put_event(EVENT_ASYNC);
|
replay_put_event(EVENT_ASYNC);
|
||||||
replay_put_byte(checkpoint);
|
|
||||||
replay_put_byte(event->event_kind);
|
replay_put_byte(event->event_kind);
|
||||||
|
|
||||||
/* save event-specific data */
|
/* save event-specific data */
|
||||||
@ -206,34 +205,27 @@ static void replay_save_event(Event *event, int checkpoint)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Called with replay mutex locked */
|
/* Called with replay mutex locked */
|
||||||
void replay_save_events(int checkpoint)
|
void replay_save_events(void)
|
||||||
{
|
{
|
||||||
g_assert(replay_mutex_locked());
|
g_assert(replay_mutex_locked());
|
||||||
g_assert(checkpoint != CHECKPOINT_CLOCK_WARP_START);
|
|
||||||
g_assert(checkpoint != CHECKPOINT_CLOCK_VIRTUAL);
|
|
||||||
while (!QTAILQ_EMPTY(&events_list)) {
|
while (!QTAILQ_EMPTY(&events_list)) {
|
||||||
Event *event = QTAILQ_FIRST(&events_list);
|
Event *event = QTAILQ_FIRST(&events_list);
|
||||||
replay_save_event(event, checkpoint);
|
replay_save_event(event);
|
||||||
replay_run_event(event);
|
replay_run_event(event);
|
||||||
QTAILQ_REMOVE(&events_list, event, events);
|
QTAILQ_REMOVE(&events_list, event, events);
|
||||||
g_free(event);
|
g_free(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Event *replay_read_event(int checkpoint)
|
static Event *replay_read_event(void)
|
||||||
{
|
{
|
||||||
Event *event;
|
Event *event;
|
||||||
if (replay_state.read_event_kind == -1) {
|
if (replay_state.read_event_kind == -1) {
|
||||||
replay_state.read_event_checkpoint = replay_get_byte();
|
|
||||||
replay_state.read_event_kind = replay_get_byte();
|
replay_state.read_event_kind = replay_get_byte();
|
||||||
replay_state.read_event_id = -1;
|
replay_state.read_event_id = -1;
|
||||||
replay_check_error();
|
replay_check_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkpoint != replay_state.read_event_checkpoint) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Events that has not to be in the queue */
|
/* Events that has not to be in the queue */
|
||||||
switch (replay_state.read_event_kind) {
|
switch (replay_state.read_event_kind) {
|
||||||
case REPLAY_ASYNC_EVENT_BH:
|
case REPLAY_ASYNC_EVENT_BH:
|
||||||
@ -294,11 +286,11 @@ static Event *replay_read_event(int checkpoint)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Called with replay mutex locked */
|
/* Called with replay mutex locked */
|
||||||
void replay_read_events(int checkpoint)
|
void replay_read_events(void)
|
||||||
{
|
{
|
||||||
g_assert(replay_mutex_locked());
|
g_assert(replay_mutex_locked());
|
||||||
while (replay_state.data_kind == EVENT_ASYNC) {
|
while (replay_state.data_kind == EVENT_ASYNC) {
|
||||||
Event *event = replay_read_event(checkpoint);
|
Event *event = replay_read_event();
|
||||||
if (!event) {
|
if (!event) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -87,8 +87,6 @@ typedef struct ReplayState {
|
|||||||
int32_t read_event_kind;
|
int32_t read_event_kind;
|
||||||
/*! Asynchronous event id read from the log */
|
/*! Asynchronous event id read from the log */
|
||||||
uint64_t read_event_id;
|
uint64_t read_event_id;
|
||||||
/*! Asynchronous event checkpoint id read from the log */
|
|
||||||
int32_t read_event_checkpoint;
|
|
||||||
} ReplayState;
|
} ReplayState;
|
||||||
extern ReplayState replay_state;
|
extern ReplayState replay_state;
|
||||||
|
|
||||||
@ -152,9 +150,9 @@ void replay_finish_events(void);
|
|||||||
/*! Returns true if there are any unsaved events in the queue */
|
/*! Returns true if there are any unsaved events in the queue */
|
||||||
bool replay_has_events(void);
|
bool replay_has_events(void);
|
||||||
/*! Saves events from queue into the file */
|
/*! Saves events from queue into the file */
|
||||||
void replay_save_events(int checkpoint);
|
void replay_save_events(void);
|
||||||
/*! Read events from the file into the input queue */
|
/*! Read events from the file into the input queue */
|
||||||
void replay_read_events(int checkpoint);
|
void replay_read_events(void);
|
||||||
/*! Adds specified async event to the queue */
|
/*! Adds specified async event to the queue */
|
||||||
void replay_add_event(ReplayAsyncEventKind event_kind, void *opaque,
|
void replay_add_event(ReplayAsyncEventKind event_kind, void *opaque,
|
||||||
void *opaque2, uint64_t id);
|
void *opaque2, uint64_t id);
|
||||||
|
@ -61,7 +61,6 @@ static const VMStateDescription vmstate_replay = {
|
|||||||
VMSTATE_UINT64(block_request_id, ReplayState),
|
VMSTATE_UINT64(block_request_id, ReplayState),
|
||||||
VMSTATE_INT32(read_event_kind, ReplayState),
|
VMSTATE_INT32(read_event_kind, ReplayState),
|
||||||
VMSTATE_UINT64(read_event_id, ReplayState),
|
VMSTATE_UINT64(read_event_id, ReplayState),
|
||||||
VMSTATE_INT32(read_event_checkpoint, ReplayState),
|
|
||||||
VMSTATE_END_OF_LIST()
|
VMSTATE_END_OF_LIST()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
/* Current version of the replay mechanism.
|
/* Current version of the replay mechanism.
|
||||||
Increase it when file format changes. */
|
Increase it when file format changes. */
|
||||||
#define REPLAY_VERSION 0xe0200a
|
#define REPLAY_VERSION 0xe0200b
|
||||||
/* Size of replay log header */
|
/* Size of replay log header */
|
||||||
#define HEADER_SIZE (sizeof(uint32_t) + sizeof(uint64_t))
|
#define HEADER_SIZE (sizeof(uint32_t) + sizeof(uint64_t))
|
||||||
|
|
||||||
@ -171,64 +171,49 @@ void replay_shutdown_request(ShutdownCause cause)
|
|||||||
|
|
||||||
bool replay_checkpoint(ReplayCheckpoint checkpoint)
|
bool replay_checkpoint(ReplayCheckpoint checkpoint)
|
||||||
{
|
{
|
||||||
bool res = false;
|
|
||||||
static bool in_checkpoint;
|
|
||||||
assert(EVENT_CHECKPOINT + checkpoint <= EVENT_CHECKPOINT_LAST);
|
assert(EVENT_CHECKPOINT + checkpoint <= EVENT_CHECKPOINT_LAST);
|
||||||
|
|
||||||
if (!replay_file) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_checkpoint) {
|
|
||||||
/*
|
|
||||||
Recursion occurs when HW event modifies timers.
|
|
||||||
Prevent performing icount warp in this case and
|
|
||||||
wait for another invocation of the checkpoint.
|
|
||||||
*/
|
|
||||||
g_assert(replay_mode == REPLAY_MODE_PLAY);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
in_checkpoint = true;
|
|
||||||
|
|
||||||
replay_save_instructions();
|
replay_save_instructions();
|
||||||
|
|
||||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||||
g_assert(replay_mutex_locked());
|
g_assert(replay_mutex_locked());
|
||||||
if (replay_next_event_is(EVENT_CHECKPOINT + checkpoint)) {
|
if (replay_next_event_is(EVENT_CHECKPOINT + checkpoint)) {
|
||||||
replay_finish_event();
|
replay_finish_event();
|
||||||
} else if (replay_state.data_kind != EVENT_ASYNC) {
|
} else {
|
||||||
res = false;
|
return false;
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
replay_read_events(checkpoint);
|
|
||||||
/* replay_read_events may leave some unread events.
|
|
||||||
Return false if not all of the events associated with
|
|
||||||
checkpoint were processed */
|
|
||||||
res = replay_state.data_kind != EVENT_ASYNC;
|
|
||||||
} else if (replay_mode == REPLAY_MODE_RECORD) {
|
} else if (replay_mode == REPLAY_MODE_RECORD) {
|
||||||
g_assert(replay_mutex_locked());
|
g_assert(replay_mutex_locked());
|
||||||
replay_put_event(EVENT_CHECKPOINT + checkpoint);
|
replay_put_event(EVENT_CHECKPOINT + checkpoint);
|
||||||
/* This checkpoint belongs to several threads.
|
|
||||||
Processing events from different threads is
|
|
||||||
non-deterministic */
|
|
||||||
if (checkpoint != CHECKPOINT_CLOCK_WARP_START
|
|
||||||
/* FIXME: this is temporary fix, other checkpoints
|
|
||||||
may also be invoked from the different threads someday.
|
|
||||||
Asynchronous event processing should be refactored
|
|
||||||
to create additional replay event kind which is
|
|
||||||
nailed to the one of the threads and which processes
|
|
||||||
the event queue. */
|
|
||||||
&& checkpoint != CHECKPOINT_CLOCK_VIRTUAL) {
|
|
||||||
replay_save_events(checkpoint);
|
|
||||||
}
|
|
||||||
res = true;
|
|
||||||
}
|
}
|
||||||
out:
|
return true;
|
||||||
in_checkpoint = false;
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool replay_has_checkpoint(void)
|
void replay_async_events(void)
|
||||||
|
{
|
||||||
|
static bool processing = false;
|
||||||
|
/*
|
||||||
|
* If we are already processing the events, recursion may occur
|
||||||
|
* in case of incorrect implementation when HW event modifies timers.
|
||||||
|
* Timer modification may invoke the icount warp, event processing,
|
||||||
|
* and cause the recursion.
|
||||||
|
*/
|
||||||
|
g_assert(!processing);
|
||||||
|
processing = true;
|
||||||
|
|
||||||
|
replay_save_instructions();
|
||||||
|
|
||||||
|
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||||
|
g_assert(replay_mutex_locked());
|
||||||
|
replay_read_events();
|
||||||
|
} else if (replay_mode == REPLAY_MODE_RECORD) {
|
||||||
|
g_assert(replay_mutex_locked());
|
||||||
|
replay_save_events();
|
||||||
|
}
|
||||||
|
processing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool replay_has_event(void)
|
||||||
{
|
{
|
||||||
bool res = false;
|
bool res = false;
|
||||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||||
@ -236,6 +221,7 @@ bool replay_has_checkpoint(void)
|
|||||||
replay_account_executed_instructions();
|
replay_account_executed_instructions();
|
||||||
res = EVENT_CHECKPOINT <= replay_state.data_kind
|
res = EVENT_CHECKPOINT <= replay_state.data_kind
|
||||||
&& replay_state.data_kind <= EVENT_CHECKPOINT_LAST;
|
&& replay_state.data_kind <= EVENT_CHECKPOINT_LAST;
|
||||||
|
res = res || replay_state.data_kind == EVENT_ASYNC;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -322,7 +322,7 @@ void icount_start_warp_timer(void)
|
|||||||
* to vCPU was processed in advance and vCPU went to sleep.
|
* to vCPU was processed in advance and vCPU went to sleep.
|
||||||
* Therefore we have to wake it up for doing someting.
|
* Therefore we have to wake it up for doing someting.
|
||||||
*/
|
*/
|
||||||
if (replay_has_checkpoint()) {
|
if (replay_has_event()) {
|
||||||
qemu_clock_notify(QEMU_CLOCK_VIRTUAL);
|
qemu_clock_notify(QEMU_CLOCK_VIRTUAL);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -404,6 +404,8 @@ void icount_account_warp_timer(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replay_async_events();
|
||||||
|
|
||||||
/* warp clock deterministically in record/replay mode */
|
/* warp clock deterministically in record/replay mode */
|
||||||
if (!replay_checkpoint(CHECKPOINT_CLOCK_WARP_ACCOUNT)) {
|
if (!replay_checkpoint(CHECKPOINT_CLOCK_WARP_ACCOUNT)) {
|
||||||
return;
|
return;
|
||||||
|
Loading…
Reference in New Issue
Block a user