Skip to content

Commit f65f9b9

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 f65f9b9

File tree

12 files changed

+162
-30
lines changed

12 files changed

+162
-30
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

core/timermanager.h

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
3131
#include <vector>
3232
#include <mutex>
3333

34-
using namespace std;
35-
3634
#define NS_PER_SEC 1000000000
3735
#define USEC_PER_SEC 1000000
3836
#define NS_PER_USEC 1000
@@ -42,7 +40,7 @@ using namespace std;
4240
#define USECS_TO_NSECS(us) (us) * NS_PER_USEC
4341
#define MSECS_TO_NSECS(ms) (ms) * NS_PER_MSEC
4442

45-
typedef function<void()> timer_cb;
43+
typedef std::function<void()> timer_cb;
4644

4745
/** Extend std::priority_queue as suggested here:
4846
https://stackoverflow.com/a/36711682
@@ -101,7 +99,7 @@ typedef struct TimerInfo {
10199
// Custom comparator for sorting our timer queue in ascending order
102100
class MyGtComparator {
103101
public:
104-
bool operator()(const shared_ptr<TimerInfo>& l, const shared_ptr<TimerInfo>& r) {
102+
bool operator()(const std::shared_ptr<TimerInfo>& l, const std::shared_ptr<TimerInfo>& r) {
105103
return l.get()->timeout_ns > r.get()->timeout_ns;
106104
}
107105
};
@@ -116,7 +114,7 @@ class TimerManager {
116114
};
117115

118116
// callback for retrieving current time
119-
void set_time_now_cb(const function<uint64_t()> &cb) {
117+
void set_time_now_cb(const std::function<uint64_t()> &cb) {
120118
this->get_time_now = cb;
121119
};
122120

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

144142
// timer queue
145-
my_priority_queue<shared_ptr<TimerInfo>, vector<shared_ptr<TimerInfo>>, MyGtComparator> timer_queue;
143+
my_priority_queue<std::shared_ptr<TimerInfo>, std::vector<std::shared_ptr<TimerInfo>>, MyGtComparator> timer_queue;
146144

147-
function<uint64_t()> get_time_now;
148-
function<void()> notify_timer_changes;
145+
std::function<uint64_t()> get_time_now;
146+
std::function<void()> notify_timer_changes;
149147

150148
std::atomic<uint32_t> id{0};
151149
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.

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: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,40 @@ 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>
33+
#include <functional>
2634
#include <loguru.hpp>
2735
#include <cubeb/cubeb.h>
2836
#ifdef _WIN32
2937
#include <objbase.h>
3038
#endif
3139

32-
enum {
40+
typedef enum {
3341
SND_SERVER_DOWN = 0,
3442
SND_API_READY,
3543
SND_SERVER_UP,
3644
SND_STREAM_OPENED,
3745
SND_STREAM_CLOSED
38-
};
46+
} Status;
3947

4048
class SoundServer::Impl {
4149
public:
42-
int status; /* server status */
50+
Status status = SND_SERVER_DOWN;
4351
cubeb *cubeb_ctx;
44-
4552
cubeb_stream *out_stream;
53+
54+
uint32_t deterministic_poll_timer = 0;
55+
std::function<void()> deterministic_poll_cb;
4656
};
4757

4858
SoundServer::SoundServer(): impl(std::make_unique<Impl>())
@@ -91,6 +101,10 @@ void SoundServer::shutdown()
91101
/* fall through */
92102
case SND_API_READY:
93103
cubeb_destroy(impl->cubeb_ctx);
104+
break;
105+
case SND_SERVER_DOWN:
106+
// Nothing to do.
107+
break;
94108
}
95109

96110
impl->status = SND_SERVER_DOWN;
@@ -148,8 +162,30 @@ static void status_callback(cubeb_stream *stream, void *user_data, cubeb_state s
148162
LOG_F(9, "Cubeb status callback fired, status = %d", state);
149163
}
150164

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

171207
res = cubeb_stream_init(impl->cubeb_ctx, &impl->out_stream, "SndOut stream",
172208
NULL, NULL, NULL, &params, latency_frames,
173-
sound_out_callback, status_callback, user_data);
209+
sound_out_callback, status_callback, dma_ch);
174210
if (res != CUBEB_OK) {
175211
LOG_F(ERROR, "Could not open sound output stream, error: %d", res);
176212
return -1;
@@ -185,11 +221,22 @@ int SoundServer::open_out_stream(uint32_t sample_rate, void *user_data)
185221

186222
int SoundServer::start_out_stream()
187223
{
224+
if (is_deterministic) {
225+
LOG_F(9, "Starting sound output deterministic polling.");
226+
impl->deterministic_poll_timer = TimerManager::get_instance()->add_cyclic_timer(MSECS_TO_NSECS(10), impl->deterministic_poll_cb);
227+
return 0;
228+
}
188229
return cubeb_stream_start(impl->out_stream);
189230
}
190231

191232
void SoundServer::close_out_stream()
192233
{
234+
if (is_deterministic) {
235+
LOG_F(9, "Stopping sound output deterministic polling.");
236+
TimerManager::get_instance()->cancel_timer(impl->deterministic_poll_timer);
237+
impl->status = SND_STREAM_CLOSED;
238+
return;
239+
}
193240
cubeb_stream_stop(impl->out_stream);
194241
cubeb_stream_destroy(impl->out_stream);
195242
impl->status = SND_STREAM_CLOSED;

0 commit comments

Comments
 (0)