diff --git a/core/coresignal.h b/core/coresignal.h index 198180f..78a1b3d 100644 --- a/core/coresignal.h +++ b/core/coresignal.h @@ -53,6 +53,9 @@ public: // Calls all connected slots. void emit(Args... args) { + if (!_is_enabled) { + return; + } for (auto const& it : _slots) { it.second(args...); } @@ -66,9 +69,22 @@ public: _slots.clear(); } + void disable() { + _is_enabled = false; + } + + void enable() { + _is_enabled = true; + } + + bool is_enabled() { + return _is_enabled; + } + private: mutable std::map> _slots; mutable unsigned int _current_id { 0 }; + mutable bool _is_enabled { true }; }; #endif // CORE_SIGNAL_H diff --git a/core/hostevents.h b/core/hostevents.h index 03e6134..cd149d5 100644 --- a/core/hostevents.h +++ b/core/hostevents.h @@ -146,6 +146,11 @@ public: _post_signal.disconnect_all(); } + void disable_input_handlers() { + _mouse_signal.disable(); + _keyboard_signal.disable(); + } + private: static EventManager* event_manager; EventManager() {}; // private constructor to implement a singleton diff --git a/core/timermanager.h b/core/timermanager.h index 465742e..51a37c3 100644 --- a/core/timermanager.h +++ b/core/timermanager.h @@ -31,8 +31,6 @@ along with this program. If not, see . #include #include -using namespace std; - #define NS_PER_SEC 1000000000 #define USEC_PER_SEC 1000000 #define NS_PER_USEC 1000 @@ -42,7 +40,7 @@ using namespace std; #define USECS_TO_NSECS(us) (us) * NS_PER_USEC #define MSECS_TO_NSECS(ms) (ms) * NS_PER_MSEC -typedef function timer_cb; +typedef std::function timer_cb; /** Extend std::priority_queue as suggested here: https://stackoverflow.com/a/36711682 @@ -101,7 +99,7 @@ typedef struct TimerInfo { // Custom comparator for sorting our timer queue in ascending order class MyGtComparator { public: - bool operator()(const shared_ptr& l, const shared_ptr& r) { + bool operator()(const std::shared_ptr& l, const std::shared_ptr& r) { return l.get()->timeout_ns > r.get()->timeout_ns; } }; @@ -116,7 +114,7 @@ public: }; // callback for retrieving current time - void set_time_now_cb(const function &cb) { + void set_time_now_cb(const std::function &cb) { this->get_time_now = cb; }; @@ -142,10 +140,10 @@ private: TimerManager(){}; // private constructor to implement a singleton // timer queue - my_priority_queue, vector>, MyGtComparator> timer_queue; + my_priority_queue, std::vector>, MyGtComparator> timer_queue; - function get_time_now; - function notify_timer_changes; + std::function get_time_now; + std::function notify_timer_changes; std::atomic id{0}; bool cb_active = false; // true if a timer callback is executing // FIXME: Do we need this? It gets written in main thread and read in audio thread. diff --git a/cpu/ppc/ppcemu.h b/cpu/ppc/ppcemu.h index 70d3f93..250913d 100644 --- a/cpu/ppc/ppcemu.h +++ b/cpu/ppc/ppcemu.h @@ -328,6 +328,9 @@ extern bool is_601; // For PowerPC 601 Emulation extern bool is_altivec; // For Altivec Emulation extern bool is_64bit; // For PowerPC G5 Emulation +// Make execution deterministic (ignore external input, used a fixed date, etc.) +extern bool is_deterministic; + // Important Addressing Integers extern uint32_t ppc_cur_instruction; extern uint32_t ppc_next_instruction_address; diff --git a/cpu/ppc/ppcexec.cpp b/cpu/ppc/ppcexec.cpp index e0fb6b2..8d8a3c8 100644 --- a/cpu/ppc/ppcexec.cpp +++ b/cpu/ppc/ppcexec.cpp @@ -57,6 +57,8 @@ MemCtrlBase* mem_ctrl_instance = 0; bool is_601 = false; +bool is_deterministic = false; + bool power_on = false; Po_Cause power_off_reason = po_enter_debugger; diff --git a/devices/common/nvram.cpp b/devices/common/nvram.cpp index 455ef0c..db21763 100644 --- a/devices/common/nvram.cpp +++ b/devices/common/nvram.cpp @@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#include #include #include #include @@ -81,6 +82,10 @@ void NVram::init() { } void NVram::save() { + if (is_deterministic) { + LOG_F(INFO, "Skipping NVRAM write to \"%s\" in deterministic mode", this->file_name.c_str()); + return; + } ofstream f(this->file_name, ios::out | ios::binary); /* write file identification */ diff --git a/devices/common/viacuda.cpp b/devices/common/viacuda.cpp index 807b3c3..33467ee 100644 --- a/devices/common/viacuda.cpp +++ b/devices/common/viacuda.cpp @@ -751,7 +751,22 @@ void ViaCuda::pseudo_command() { } uint32_t ViaCuda::calc_real_time() { - auto end = std::chrono::system_clock::now(); + std::chrono::time_point end; + if (is_deterministic) { + // March 24, 2001 was the public release date of Mac OS X. + std::tm tm = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 12, + .tm_mday = 24, + .tm_mon = 3 - 1, + .tm_year = 2001 - 1900, + .tm_isdst = 0 + }; + end = std::chrono::system_clock::from_time_t(std::mktime(&tm)); + } else { + end = std::chrono::system_clock::now(); + } auto elapsed_systemclock = end - this->mac_epoch; auto elapsed_seconds = std::chrono::duration_cast(elapsed_systemclock); return uint32_t(elapsed_seconds.count()); diff --git a/devices/sound/awacs.cpp b/devices/sound/awacs.cpp index 863f10c..2f8449c 100644 --- a/devices/sound/awacs.cpp +++ b/devices/sound/awacs.cpp @@ -69,8 +69,7 @@ void AwacsBase::dma_out_start() { } if (!this->out_stream_ready) { - if ((err = this->snd_server->open_out_stream(this->cur_sample_rate, - (void *)this->dma_out_ch))) { + if ((err = this->snd_server->open_out_stream(this->cur_sample_rate, this->dma_out_ch))) { LOG_F(ERROR, "%s: unable to open sound output stream: %d", this->name.c_str(), err); return; diff --git a/devices/sound/soundserver.h b/devices/sound/soundserver.h index cd74e99..8950f26 100644 --- a/devices/sound/soundserver.h +++ b/devices/sound/soundserver.h @@ -38,6 +38,8 @@ along with this program. If not, see . #include +class DmaOutChannel; + class SoundServer : public HWComponent { public: SoundServer(); @@ -45,7 +47,7 @@ public: int start(); void shutdown(); - int open_out_stream(uint32_t sample_rate, void *user_data); + int open_out_stream(uint32_t sample_rate, DmaOutChannel *dma_ch); int start_out_stream(); void close_out_stream(); diff --git a/devices/sound/soundserver_cubeb.cpp b/devices/sound/soundserver_cubeb.cpp index 9831e68..f70bf0e 100644 --- a/devices/sound/soundserver_cubeb.cpp +++ b/devices/sound/soundserver_cubeb.cpp @@ -19,30 +19,40 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include #include #include #include +#include +#include #include #include #ifdef _WIN32 #include #endif -enum { +typedef enum { SND_SERVER_DOWN = 0, SND_API_READY, SND_SERVER_UP, SND_STREAM_OPENED, SND_STREAM_CLOSED -}; +} Status; class SoundServer::Impl { public: - int status; /* server status */ + Status status = SND_SERVER_DOWN; cubeb *cubeb_ctx; - cubeb_stream *out_stream; + + uint32_t deterministic_poll_timer = 0; + std::function deterministic_poll_cb; }; SoundServer::SoundServer(): impl(std::make_unique()) @@ -91,6 +101,10 @@ void SoundServer::shutdown() /* fall through */ case SND_API_READY: cubeb_destroy(impl->cubeb_ctx); + break; + case SND_SERVER_DOWN: + // Nothing to do. + break; } impl->status = SND_SERVER_DOWN; @@ -148,8 +162,30 @@ static void status_callback(cubeb_stream *stream, void *user_data, cubeb_state s LOG_F(9, "Cubeb status callback fired, status = %d", state); } -int SoundServer::open_out_stream(uint32_t sample_rate, void *user_data) +int SoundServer::open_out_stream(uint32_t sample_rate, DmaOutChannel *dma_ch) { + if (is_deterministic) { + impl->deterministic_poll_cb = [dma_ch] { + if (!dma_ch->is_out_active()) { + return; + } + // Drain the DMA buffer, but don't do anything else. + int req_size = std::max(dma_ch->get_pull_data_remaining(), 1024); + int out_size = 0; + while (req_size > 0) { + uint8_t *chunk; + uint32_t chunk_size; + if (!dma_ch->pull_data(req_size, &chunk_size, &chunk)) { + req_size -= chunk_size; + } else { + break; + } + } + }; + impl->status = SND_STREAM_OPENED; + LOG_F(9, "Deterministic sound output callback set up."); + return 0; + } int res; uint32_t latency_frames; cubeb_stream_params params; @@ -170,7 +206,7 @@ int SoundServer::open_out_stream(uint32_t sample_rate, void *user_data) res = cubeb_stream_init(impl->cubeb_ctx, &impl->out_stream, "SndOut stream", NULL, NULL, NULL, ¶ms, latency_frames, - sound_out_callback, status_callback, user_data); + sound_out_callback, status_callback, dma_ch); if (res != CUBEB_OK) { LOG_F(ERROR, "Could not open sound output stream, error: %d", res); return -1; @@ -185,11 +221,22 @@ int SoundServer::open_out_stream(uint32_t sample_rate, void *user_data) int SoundServer::start_out_stream() { + if (is_deterministic) { + LOG_F(9, "Starting sound output deterministic polling."); + impl->deterministic_poll_timer = TimerManager::get_instance()->add_cyclic_timer(MSECS_TO_NSECS(10), impl->deterministic_poll_cb); + return 0; + } return cubeb_stream_start(impl->out_stream); } void SoundServer::close_out_stream() { + if (is_deterministic) { + LOG_F(9, "Stopping sound output deterministic polling."); + TimerManager::get_instance()->cancel_timer(impl->deterministic_poll_timer); + impl->status = SND_STREAM_CLOSED; + return; + } cubeb_stream_stop(impl->out_stream); cubeb_stream_destroy(impl->out_stream); impl->status = SND_STREAM_CLOSED; diff --git a/main.cpp b/main.cpp index 4e67e49..382d75c 100644 --- a/main.cpp +++ b/main.cpp @@ -25,6 +25,7 @@ along with this program. If not, see . #include #include #include +#include #include #include #include @@ -82,6 +83,8 @@ int main(int argc, char** argv) { "Enter the built-in debugger"); app.add_option("-b,--bootrom", bootrom_path, "Specifies BootROM path") ->check(CLI::ExistingFile); + app.add_flag("--deterministic", is_deterministic, + "Make execution deterministic"); bool log_to_stderr = false; loguru::Verbosity log_verbosity = loguru::Verbosity_INFO; @@ -177,6 +180,9 @@ int main(int argc, char** argv) { cout << "BootROM path: " << bootrom_path << endl; cout << "Execution mode: " << execution_mode << endl; + if (is_deterministic) { + cout << "Using deterministic execution mode, input will be ignored." << endl; + } if (!init()) { LOG_F(ERROR, "Cannot initialize"); @@ -233,6 +239,21 @@ void run_machine(std::string machine_str, std::string bootrom_path, uint32_t exe return; } + uint32_t deterministic_timer; + if (is_deterministic) { + EventManager::get_instance()->disable_input_handlers(); + // Log the PC and instruction every second to make it easier to validate + // that execution is the same every time. + deterministic_timer = TimerManager::get_instance()->add_cyclic_timer(MSECS_TO_NSECS(100), [] { + PPCDisasmContext ctx; + ctx.instr_code = ppc_cur_instruction; + ctx.instr_addr = 0; + ctx.simplified = false; + auto op_name = disassemble_single(&ctx); + LOG_F(INFO, "TS=%016llu PC=0x%08x executing %s", get_virt_time_ns(), ppc_state.pc, op_name.c_str()); + }); + } + // set up system wide event polling using // default Macintosh polling rate of 11 ms 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 TimerManager::get_instance()->cancel_timer(profiling_timer); } #endif + if (is_deterministic) { + TimerManager::get_instance()->cancel_timer(deterministic_timer); + } EventManager::get_instance()->disconnect_handlers(); delete gMachineObj.release(); } diff --git a/utils/imgfile_sdl.cpp b/utils/imgfile_sdl.cpp index 30d6b5f..a5d80c8 100644 --- a/utils/imgfile_sdl.cpp +++ b/utils/imgfile_sdl.cpp @@ -22,10 +22,14 @@ along with this program. If not, see . #include #include +#include +#include + +extern bool is_deterministic; class ImgFile::Impl { public: - std::fstream stream; + std::unique_ptr stream; }; ImgFile::ImgFile(): impl(std::make_unique()) @@ -37,31 +41,43 @@ ImgFile::~ImgFile() = default; bool ImgFile::open(const std::string &img_path) { - impl->stream.open(img_path, std::ios::in | std::ios::out | std::ios::binary); - return !impl->stream.fail(); + if (is_deterministic) { + // Avoid writes to the underlying file by reading it all in memory and + // only operating on that. + auto mem_stream = std::make_unique(); + std::ifstream temp(img_path, std::ios::in | std::ios::binary); + if (!temp) return false; + *mem_stream << temp.rdbuf(); + impl->stream = std::move(mem_stream); + } else { + auto file_stream = std::make_unique(img_path, std::ios::in | std::ios::out | std::ios::binary); + if (!file_stream->is_open()) return false; + impl->stream = std::move(file_stream); + } + return impl->stream && impl->stream->good(); } void ImgFile::close() { - impl->stream.close(); + impl->stream.reset(); } uint64_t ImgFile::size() const { - impl->stream.seekg(0, impl->stream.end); - return impl->stream.tellg(); + impl->stream->seekg(0, impl->stream->end); + return impl->stream->tellg(); } uint64_t ImgFile::read(void* buf, uint64_t offset, uint64_t length) const { - impl->stream.seekg(offset, std::ios::beg); - impl->stream.read((char *)buf, length); - return impl->stream.gcount(); + impl->stream->seekg(offset, std::ios::beg); + impl->stream->read((char *)buf, length); + return impl->stream->gcount(); } uint64_t ImgFile::write(const void* buf, uint64_t offset, uint64_t length) { - impl->stream.seekg(offset, std::ios::beg); - impl->stream.write((const char *)buf, length); - return impl->stream.gcount(); + impl->stream->seekg(offset, std::ios::beg); + impl->stream->write((const char *)buf, length); + return impl->stream->gcount(); }