I remember some discussion a while ago about how to safely and portably save and restore the "intermediate state" of various co-threads. The problem is that except at certain clearly-defined points (like at instruction boundaries), the co-thread will be in the middle of some computation and intermediate state will be stored in various places (e.g. host machine registers). Even if you could identify and save all of the places containing that intermediate state, it is in a very non-portable form (it depends on host machine type + compiler + particular build).
Here is an approach I thought of to work around that problem. I explain it here in terms of an emulated CPU, but analogous steps should work for any emulated device:
(1) arrange for your Read and Write functions to append the data value in a buffer.
(2) when you actually save the emu state, save the registers as of the last instruction boundary, but include the contents of this buffer in the saved state.
(3) when you restore the emu state, your Read and Write functions should use the buffer instead of performing real accesses, until the buffer runs out.
In other words, save the state as it was at the instruction boundary, but include enough info to re-emulate part of that instruction *without any side effects*. From the point of view of other co-threads that might be affected by your side effects, this is equivalent to saving and restoring the intermediate state.
For performance reasons you might want a way to enable and disable this "extra buffer stuff" in your Read and Write function(s). For an emu written in assembler, you could patch it into the functions only when it is needed, and remove it after.
So to save the entire emulator state at the very beginning of your "frame", you might do the following:
- Advance each co-thread until it is close to the desired "tick" (smallest time unit you can pause at, e.g. for an SNES CPU it would be a master cycle). This probably means stopping 1-2 "instructions" before the desired tick
- Capture the state of each co-thread at the "instruction boundary"
- Install the extra logging stuff in the Read/Write functions of each co-thread (it should also include the ability to suspend each co-thread at any given tick)
- Advance each co-thread one at a time until it reaches exactly the desired tick
- Restore the normal Read/Write behaviour and resume running
To load an entire emulator state, you would:
- Read all the data back from the file, and set up each co-thread at the "instruction boundary" it was saved at (restore timers, etc)
- Install the log playback version of Read/Write functions for each co-thread
- Advance each co-thread one at a time until it runs out of logged data, stopping it at THAT VERY TICK
- Restore the normal Read/Write behaviour and resume running
Notice that even if the logging versions record MORE than an entire instruction's worth of reads and writes, it will still work correctly.
Something like this might be useful for emulators which want to save exactly at a certain point (i.e. the start of a frame). You effectively capture some previous state of each task, plus all the side effects that occurred while advancing the task from that previous state til EXACTLY the point you're interested in.
Here is an approach I thought of to work around that problem. I explain it here in terms of an emulated CPU, but analogous steps should work for any emulated device:
(1) arrange for your Read and Write functions to append the data value in a buffer.
(2) when you actually save the emu state, save the registers as of the last instruction boundary, but include the contents of this buffer in the saved state.
(3) when you restore the emu state, your Read and Write functions should use the buffer instead of performing real accesses, until the buffer runs out.
In other words, save the state as it was at the instruction boundary, but include enough info to re-emulate part of that instruction *without any side effects*. From the point of view of other co-threads that might be affected by your side effects, this is equivalent to saving and restoring the intermediate state.
For performance reasons you might want a way to enable and disable this "extra buffer stuff" in your Read and Write function(s). For an emu written in assembler, you could patch it into the functions only when it is needed, and remove it after.
So to save the entire emulator state at the very beginning of your "frame", you might do the following:
- Advance each co-thread until it is close to the desired "tick" (smallest time unit you can pause at, e.g. for an SNES CPU it would be a master cycle). This probably means stopping 1-2 "instructions" before the desired tick
- Capture the state of each co-thread at the "instruction boundary"
- Install the extra logging stuff in the Read/Write functions of each co-thread (it should also include the ability to suspend each co-thread at any given tick)
- Advance each co-thread one at a time until it reaches exactly the desired tick
- Restore the normal Read/Write behaviour and resume running
To load an entire emulator state, you would:
- Read all the data back from the file, and set up each co-thread at the "instruction boundary" it was saved at (restore timers, etc)
- Install the log playback version of Read/Write functions for each co-thread
- Advance each co-thread one at a time until it runs out of logged data, stopping it at THAT VERY TICK
- Restore the normal Read/Write behaviour and resume running
Notice that even if the logging versions record MORE than an entire instruction's worth of reads and writes, it will still work correctly.
Something like this might be useful for emulators which want to save exactly at a certain point (i.e. the start of a frame). You effectively capture some previous state of each task, plus all the side effects that occurred while advancing the task from that previous state til EXACTLY the point you're interested in.