Skip to content

Commit

Permalink
Merge pull request #120 from mihaip/upstream-deterministic
Browse files Browse the repository at this point in the history
Add a deterministic execution mode
  • Loading branch information
dingusdev authored Nov 10, 2024
2 parents f415a63 + f65f9b9 commit 42f1871
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 30 deletions.
16 changes: 16 additions & 0 deletions core/coresignal.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ class CoreSignal {

// Calls all connected slots.
void emit(Args... args) {
if (!_is_enabled) {
return;
}
for (auto const& it : _slots) {
it.second(args...);
}
Expand All @@ -66,9 +69,22 @@ class CoreSignal {
_slots.clear();
}

void disable() {
_is_enabled = false;
}

void enable() {
_is_enabled = true;
}

bool is_enabled() {
return _is_enabled;
}

private:
mutable std::map<int, std::function<void(Args...)>> _slots;
mutable unsigned int _current_id { 0 };
mutable bool _is_enabled { true };
};

#endif // CORE_SIGNAL_H
5 changes: 5 additions & 0 deletions core/hostevents.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ class EventManager {
_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
Expand Down
14 changes: 6 additions & 8 deletions core/timermanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <vector>
#include <mutex>

using namespace std;

#define NS_PER_SEC 1000000000
#define USEC_PER_SEC 1000000
#define NS_PER_USEC 1000
Expand All @@ -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<void()> timer_cb;
typedef std::function<void()> timer_cb;

/** Extend std::priority_queue as suggested here:
https://stackoverflow.com/a/36711682
Expand Down Expand Up @@ -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<TimerInfo>& l, const shared_ptr<TimerInfo>& r) {
bool operator()(const std::shared_ptr<TimerInfo>& l, const std::shared_ptr<TimerInfo>& r) {
return l.get()->timeout_ns > r.get()->timeout_ns;
}
};
Expand All @@ -116,7 +114,7 @@ class TimerManager {
};

// callback for retrieving current time
void set_time_now_cb(const function<uint64_t()> &cb) {
void set_time_now_cb(const std::function<uint64_t()> &cb) {
this->get_time_now = cb;
};

Expand All @@ -142,10 +140,10 @@ class TimerManager {
TimerManager(){}; // private constructor to implement a singleton

// timer queue
my_priority_queue<shared_ptr<TimerInfo>, vector<shared_ptr<TimerInfo>>, MyGtComparator> timer_queue;
my_priority_queue<std::shared_ptr<TimerInfo>, std::vector<std::shared_ptr<TimerInfo>>, MyGtComparator> timer_queue;

function<uint64_t()> get_time_now;
function<void()> notify_timer_changes;
std::function<uint64_t()> get_time_now;
std::function<void()> notify_timer_changes;

std::atomic<uint32_t> 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.
Expand Down
3 changes: 3 additions & 0 deletions cpu/ppc/ppcemu.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions cpu/ppc/ppcexec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
5 changes: 5 additions & 0 deletions devices/common/nvram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include <cpu/ppc/ppcemu.h>
#include <devices/common/hwcomponent.h>
#include <devices/common/nvram.h>
#include <devices/deviceregistry.h>
Expand Down Expand Up @@ -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 */
Expand Down
17 changes: 16 additions & 1 deletion devices/common/viacuda.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::chrono::system_clock> 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<std::chrono::seconds>(elapsed_systemclock);
return uint32_t(elapsed_seconds.count());
Expand Down
3 changes: 1 addition & 2 deletions devices/sound/awacs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion devices/sound/soundserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.

#include <memory>

class DmaOutChannel;

class SoundServer : public HWComponent {
public:
SoundServer();
~SoundServer();

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();

Expand Down
59 changes: 53 additions & 6 deletions devices/sound/soundserver_cubeb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,40 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#ifndef NOMINMAX
#define NOMINMAX
#endif // NOMINMAX

#include <core/timermanager.h>
#include <cpu/ppc/ppcemu.h>
#include <devices/common/dmacore.h>
#include <devices/sound/soundserver.h>
#include <endianswap.h>

#include <algorithm>
#include <functional>
#include <loguru.hpp>
#include <cubeb/cubeb.h>
#ifdef _WIN32
#include <objbase.h>
#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<void()> deterministic_poll_cb;
};

SoundServer::SoundServer(): impl(std::make_unique<Impl>())
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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, &params, 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;
Expand All @@ -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;
Expand Down
24 changes: 24 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <core/hostevents.h>
#include <core/timermanager.h>
#include <cpu/ppc/ppcemu.h>
#include <cpu/ppc/ppcdisasm.h>
#include <debugger/debugger.h>
#include <machines/machinebase.h>
#include <machines/machinefactory.h>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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), [] {
Expand Down Expand Up @@ -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();
}
Loading

0 comments on commit 42f1871

Please sign in to comment.