Skip to content

Commit d206030

Browse files
committed
Add a deterministic execution mode
Adds support for a --deterministic command-line option that makes repeated runs the same: - Keyboard and mouse input is ignored - The sound server does a periodic pull from the DMA channel (so that it gets drained), but only does so via a periodic timer (instead of being driven by a cubeb callback, which could arrive at different times) - Disk image writes are disabled (reads of a modified area still work via an in-memory copy) - NVRAM writes are disabled - The current time that ViaCuda initializes the guest OS is always the same. This makes execution exactly the same each time, which should make debugging of more subtle issues easier. To validate that the deterministic mode is working, I've added a periodic log of the current "time" (measured in cycle count), PC and opcode. When comparing two runs with --log-no-uptime, the generated log files are identical.
1 parent f415a63 commit d206030

File tree

11 files changed

+155
-22
lines changed

11 files changed

+155
-22
lines changed

core/coresignal.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ class CoreSignal {
5353

5454
// Calls all connected slots.
5555
void emit(Args... args) {
56+
if (!_is_enabled) {
57+
return;
58+
}
5659
for (auto const& it : _slots) {
5760
it.second(args...);
5861
}
@@ -66,9 +69,22 @@ class CoreSignal {
6669
_slots.clear();
6770
}
6871

72+
void disable() {
73+
_is_enabled = false;
74+
}
75+
76+
void enable() {
77+
_is_enabled = true;
78+
}
79+
80+
bool is_enabled() {
81+
return _is_enabled;
82+
}
83+
6984
private:
7085
mutable std::map<int, std::function<void(Args...)>> _slots;
7186
mutable unsigned int _current_id { 0 };
87+
mutable bool _is_enabled { true };
7288
};
7389

7490
#endif // CORE_SIGNAL_H

core/hostevents.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ class EventManager {
146146
_post_signal.disconnect_all();
147147
}
148148

149+
void disable_input_handlers() {
150+
_mouse_signal.disable();
151+
_keyboard_signal.disable();
152+
}
153+
149154
private:
150155
static EventManager* event_manager;
151156
EventManager() {}; // private constructor to implement a singleton

cpu/ppc/ppcemu.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,9 @@ extern bool is_601; // For PowerPC 601 Emulation
328328
extern bool is_altivec; // For Altivec Emulation
329329
extern bool is_64bit; // For PowerPC G5 Emulation
330330

331+
// Make execution deterministic (ignore external input, used a fixed date, etc.)
332+
extern bool is_deterministic;
333+
331334
// Important Addressing Integers
332335
extern uint32_t ppc_cur_instruction;
333336
extern uint32_t ppc_next_instruction_address;

cpu/ppc/ppcexec.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ MemCtrlBase* mem_ctrl_instance = 0;
5757

5858
bool is_601 = false;
5959

60+
bool is_deterministic = false;
61+
6062
bool power_on = false;
6163
Po_Cause power_off_reason = po_enter_debugger;
6264

devices/common/nvram.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License
1919
along with this program. If not, see <https://www.gnu.org/licenses/>.
2020
*/
2121

22+
#include <cpu/ppc/ppcemu.h>
2223
#include <devices/common/hwcomponent.h>
2324
#include <devices/common/nvram.h>
2425
#include <devices/deviceregistry.h>
@@ -81,6 +82,10 @@ void NVram::init() {
8182
}
8283

8384
void NVram::save() {
85+
if (is_deterministic) {
86+
LOG_F(INFO, "Skipping NVRAM write to \"%s\" in deterministic mode", this->file_name.c_str());
87+
return;
88+
}
8489
ofstream f(this->file_name, ios::out | ios::binary);
8590

8691
/* write file identification */

devices/common/viacuda.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,22 @@ void ViaCuda::pseudo_command() {
751751
}
752752

753753
uint32_t ViaCuda::calc_real_time() {
754-
auto end = std::chrono::system_clock::now();
754+
std::chrono::time_point<std::chrono::system_clock> end;
755+
if (is_deterministic) {
756+
// March 24, 2001 was the public release date of Mac OS X.
757+
std::tm tm = {
758+
.tm_sec = 0,
759+
.tm_min = 0,
760+
.tm_hour = 12,
761+
.tm_mday = 24,
762+
.tm_mon = 3 - 1,
763+
.tm_year = 2001 - 1900,
764+
.tm_isdst = 0
765+
};
766+
end = std::chrono::system_clock::from_time_t(std::mktime(&tm));
767+
} else {
768+
end = std::chrono::system_clock::now();
769+
}
755770
auto elapsed_systemclock = end - this->mac_epoch;
756771
auto elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed_systemclock);
757772
return uint32_t(elapsed_seconds.count());

devices/sound/awacs.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ void AwacsBase::dma_out_start() {
6969
}
7070

7171
if (!this->out_stream_ready) {
72-
if ((err = this->snd_server->open_out_stream(this->cur_sample_rate,
73-
(void *)this->dma_out_ch))) {
72+
if ((err = this->snd_server->open_out_stream(this->cur_sample_rate, this->dma_out_ch))) {
7473
LOG_F(ERROR, "%s: unable to open sound output stream: %d",
7574
this->name.c_str(), err);
7675
return;

devices/sound/soundserver.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
3838

3939
#include <memory>
4040

41+
class DmaOutChannel;
42+
4143
class SoundServer : public HWComponent {
4244
public:
4345
SoundServer();
4446
~SoundServer();
4547

4648
int start();
4749
void shutdown();
48-
int open_out_stream(uint32_t sample_rate, void *user_data);
50+
int open_out_stream(uint32_t sample_rate, DmaOutChannel *dma_ch);
4951
int start_out_stream();
5052
void close_out_stream();
5153

devices/sound/soundserver_cubeb.cpp

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,39 @@ You should have received a copy of the GNU General Public License
1919
along with this program. If not, see <https://www.gnu.org/licenses/>.
2020
*/
2121

22+
#ifndef NOMINMAX
23+
#define NOMINMAX
24+
#endif // NOMINMAX
25+
26+
#include <core/timermanager.h>
27+
#include <cpu/ppc/ppcemu.h>
2228
#include <devices/common/dmacore.h>
2329
#include <devices/sound/soundserver.h>
2430
#include <endianswap.h>
2531

32+
#include <algorithm>
2633
#include <loguru.hpp>
2734
#include <cubeb/cubeb.h>
2835
#ifdef _WIN32
2936
#include <objbase.h>
3037
#endif
3138

32-
enum {
39+
typedef enum {
3340
SND_SERVER_DOWN = 0,
3441
SND_API_READY,
3542
SND_SERVER_UP,
3643
SND_STREAM_OPENED,
3744
SND_STREAM_CLOSED
38-
};
45+
} Status;
3946

4047
class SoundServer::Impl {
4148
public:
42-
int status; /* server status */
49+
Status status = SND_SERVER_DOWN;
4350
cubeb *cubeb_ctx;
44-
4551
cubeb_stream *out_stream;
52+
53+
uint32_t deterministic_poll_timer = 0;
54+
std::function<void()> deterministic_poll_cb;
4655
};
4756

4857
SoundServer::SoundServer(): impl(std::make_unique<Impl>())
@@ -91,6 +100,10 @@ void SoundServer::shutdown()
91100
/* fall through */
92101
case SND_API_READY:
93102
cubeb_destroy(impl->cubeb_ctx);
103+
break;
104+
case SND_SERVER_DOWN:
105+
// Nothing to do.
106+
break;
94107
}
95108

96109
impl->status = SND_SERVER_DOWN;
@@ -148,8 +161,30 @@ static void status_callback(cubeb_stream *stream, void *user_data, cubeb_state s
148161
LOG_F(9, "Cubeb status callback fired, status = %d", state);
149162
}
150163

151-
int SoundServer::open_out_stream(uint32_t sample_rate, void *user_data)
164+
int SoundServer::open_out_stream(uint32_t sample_rate, DmaOutChannel *dma_ch)
152165
{
166+
if (is_deterministic) {
167+
impl->deterministic_poll_cb = [dma_ch] {
168+
if (!dma_ch->is_out_active()) {
169+
return;
170+
}
171+
// Drain the DMA buffer, but don't do anything else.
172+
int req_size = std::max(dma_ch->get_pull_data_remaining(), 1024);
173+
int out_size = 0;
174+
while (req_size > 0) {
175+
uint8_t *chunk;
176+
uint32_t chunk_size;
177+
if (!dma_ch->pull_data(req_size, &chunk_size, &chunk)) {
178+
req_size -= chunk_size;
179+
} else {
180+
break;
181+
}
182+
}
183+
};
184+
impl->status = SND_STREAM_OPENED;
185+
LOG_F(9, "Deterministic sound output callback set up.");
186+
return 0;
187+
}
153188
int res;
154189
uint32_t latency_frames;
155190
cubeb_stream_params params;
@@ -170,7 +205,7 @@ int SoundServer::open_out_stream(uint32_t sample_rate, void *user_data)
170205

171206
res = cubeb_stream_init(impl->cubeb_ctx, &impl->out_stream, "SndOut stream",
172207
NULL, NULL, NULL, &params, latency_frames,
173-
sound_out_callback, status_callback, user_data);
208+
sound_out_callback, status_callback, dma_ch);
174209
if (res != CUBEB_OK) {
175210
LOG_F(ERROR, "Could not open sound output stream, error: %d", res);
176211
return -1;
@@ -185,11 +220,22 @@ int SoundServer::open_out_stream(uint32_t sample_rate, void *user_data)
185220

186221
int SoundServer::start_out_stream()
187222
{
223+
if (is_deterministic) {
224+
LOG_F(9, "Starting sound output deterministic polling.");
225+
impl->deterministic_poll_timer = TimerManager::get_instance()->add_cyclic_timer(MSECS_TO_NSECS(10), impl->deterministic_poll_cb);
226+
return 0;
227+
}
188228
return cubeb_stream_start(impl->out_stream);
189229
}
190230

191231
void SoundServer::close_out_stream()
192232
{
233+
if (is_deterministic) {
234+
LOG_F(9, "Stopping sound output deterministic polling.");
235+
TimerManager::get_instance()->cancel_timer(impl->deterministic_poll_timer);
236+
impl->status = SND_STREAM_CLOSED;
237+
return;
238+
}
193239
cubeb_stream_stop(impl->out_stream);
194240
cubeb_stream_destroy(impl->out_stream);
195241
impl->status = SND_STREAM_CLOSED;

main.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
2525
#include <core/hostevents.h>
2626
#include <core/timermanager.h>
2727
#include <cpu/ppc/ppcemu.h>
28+
#include <cpu/ppc/ppcdisasm.h>
2829
#include <debugger/debugger.h>
2930
#include <machines/machinebase.h>
3031
#include <machines/machinefactory.h>
@@ -82,6 +83,8 @@ int main(int argc, char** argv) {
8283
"Enter the built-in debugger");
8384
app.add_option("-b,--bootrom", bootrom_path, "Specifies BootROM path")
8485
->check(CLI::ExistingFile);
86+
app.add_flag("--deterministic", is_deterministic,
87+
"Make execution deterministic");
8588

8689
bool log_to_stderr = false;
8790
loguru::Verbosity log_verbosity = loguru::Verbosity_INFO;
@@ -177,6 +180,9 @@ int main(int argc, char** argv) {
177180

178181
cout << "BootROM path: " << bootrom_path << endl;
179182
cout << "Execution mode: " << execution_mode << endl;
183+
if (is_deterministic) {
184+
cout << "Using deterministic execution mode, input will be ignored." << endl;
185+
}
180186

181187
if (!init()) {
182188
LOG_F(ERROR, "Cannot initialize");
@@ -233,6 +239,21 @@ void run_machine(std::string machine_str, std::string bootrom_path, uint32_t exe
233239
return;
234240
}
235241

242+
uint32_t deterministic_timer;
243+
if (is_deterministic) {
244+
EventManager::get_instance()->disable_input_handlers();
245+
// Log the PC and instruction every second to make it easier to validate
246+
// that execution is the same every time.
247+
deterministic_timer = TimerManager::get_instance()->add_cyclic_timer(MSECS_TO_NSECS(100), [] {
248+
PPCDisasmContext ctx;
249+
ctx.instr_code = ppc_cur_instruction;
250+
ctx.instr_addr = 0;
251+
ctx.simplified = false;
252+
auto op_name = disassemble_single(&ctx);
253+
LOG_F(INFO, "TS=%016llu PC=0x%08x executing %s", get_virt_time_ns(), ppc_state.pc, op_name.c_str());
254+
});
255+
}
256+
236257
// set up system wide event polling using
237258
// default Macintosh polling rate of 11 ms
238259
uint32_t event_timer = TimerManager::get_instance()->add_cyclic_timer(MSECS_TO_NSECS(11), [] {
@@ -273,6 +294,9 @@ void run_machine(std::string machine_str, std::string bootrom_path, uint32_t exe
273294
TimerManager::get_instance()->cancel_timer(profiling_timer);
274295
}
275296
#endif
297+
if (is_deterministic) {
298+
TimerManager::get_instance()->cancel_timer(deterministic_timer);
299+
}
276300
EventManager::get_instance()->disconnect_handlers();
277301
delete gMachineObj.release();
278302
}

0 commit comments

Comments
 (0)