Profiling #7
			
				
			
		
		
		
	
							
								
								
									
										2
									
								
								build.rs
								
								
								
								
							
							
						
						
									
										2
									
								
								build.rs
								
								
								
								
							| 
						 | 
					@ -23,7 +23,9 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
        .define("VB_LITTLE_ENDIAN", None)
 | 
					        .define("VB_LITTLE_ENDIAN", None)
 | 
				
			||||||
        .define("VB_SIGNED_PROPAGATE", None)
 | 
					        .define("VB_SIGNED_PROPAGATE", None)
 | 
				
			||||||
        .define("VB_DIV_GENERIC", None)
 | 
					        .define("VB_DIV_GENERIC", None)
 | 
				
			||||||
 | 
					        .define("VB_DIRECT_EXCEPTION", "on_exception")
 | 
				
			||||||
        .define("VB_DIRECT_EXECUTE", "on_execute")
 | 
					        .define("VB_DIRECT_EXECUTE", "on_execute")
 | 
				
			||||||
 | 
					        .define("VB_DIRECT_FETCH", "on_fetch")
 | 
				
			||||||
        .define("VB_DIRECT_FRAME", "on_frame")
 | 
					        .define("VB_DIRECT_FRAME", "on_frame")
 | 
				
			||||||
        .define("VB_DIRECT_READ", "on_read")
 | 
					        .define("VB_DIRECT_READ", "on_read")
 | 
				
			||||||
        .define("VB_DIRECT_WRITE", "on_write")
 | 
					        .define("VB_DIRECT_WRITE", "on_write")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ use tracing::{error, warn};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    audio::Audio,
 | 
					    audio::Audio,
 | 
				
			||||||
    emulator::cart::Cart,
 | 
					    emulator::{cart::Cart, profiler::Profiler},
 | 
				
			||||||
    graphics::TextureSink,
 | 
					    graphics::TextureSink,
 | 
				
			||||||
    memory::{MemoryRange, MemoryRegion},
 | 
					    memory::{MemoryRange, MemoryRegion},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,7 @@ pub use shrooms_vb_core::{VBKey, VBRegister, VBWatchpointType};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod address_set;
 | 
					mod address_set;
 | 
				
			||||||
mod cart;
 | 
					mod cart;
 | 
				
			||||||
 | 
					mod profiler;
 | 
				
			||||||
mod shrooms_vb_core;
 | 
					mod shrooms_vb_core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
 | 
					#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
 | 
				
			||||||
| 
						 | 
					@ -69,6 +70,7 @@ pub struct EmulatorBuilder {
 | 
				
			||||||
    audio_on: Arc<[AtomicBool; 2]>,
 | 
					    audio_on: Arc<[AtomicBool; 2]>,
 | 
				
			||||||
    linked: Arc<AtomicBool>,
 | 
					    linked: Arc<AtomicBool>,
 | 
				
			||||||
    start_paused: bool,
 | 
					    start_paused: bool,
 | 
				
			||||||
 | 
					    monitor_events: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl EmulatorBuilder {
 | 
					impl EmulatorBuilder {
 | 
				
			||||||
| 
						 | 
					@ -85,6 +87,7 @@ impl EmulatorBuilder {
 | 
				
			||||||
            audio_on: Arc::new([AtomicBool::new(true), AtomicBool::new(true)]),
 | 
					            audio_on: Arc::new([AtomicBool::new(true), AtomicBool::new(true)]),
 | 
				
			||||||
            linked: Arc::new(AtomicBool::new(false)),
 | 
					            linked: Arc::new(AtomicBool::new(false)),
 | 
				
			||||||
            start_paused: false,
 | 
					            start_paused: false,
 | 
				
			||||||
 | 
					            monitor_events: false,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let client = EmulatorClient {
 | 
					        let client = EmulatorClient {
 | 
				
			||||||
            queue,
 | 
					            queue,
 | 
				
			||||||
| 
						 | 
					@ -110,6 +113,13 @@ impl EmulatorBuilder {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn monitor_events(self, monitor_events: bool) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            monitor_events,
 | 
				
			||||||
 | 
					            ..self
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn build(self) -> Result<Emulator> {
 | 
					    pub fn build(self) -> Result<Emulator> {
 | 
				
			||||||
        let mut emulator = Emulator::new(
 | 
					        let mut emulator = Emulator::new(
 | 
				
			||||||
            self.commands,
 | 
					            self.commands,
 | 
				
			||||||
| 
						 | 
					@ -124,6 +134,9 @@ impl EmulatorBuilder {
 | 
				
			||||||
        if self.start_paused {
 | 
					        if self.start_paused {
 | 
				
			||||||
            emulator.pause_sims()?;
 | 
					            emulator.pause_sims()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if self.monitor_events {
 | 
				
			||||||
 | 
					            emulator.monitor_events(SimId::Player1)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        Ok(emulator)
 | 
					        Ok(emulator)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -137,6 +150,7 @@ pub struct Emulator {
 | 
				
			||||||
    state: Arc<Atomic<EmulatorState>>,
 | 
					    state: Arc<Atomic<EmulatorState>>,
 | 
				
			||||||
    audio_on: Arc<[AtomicBool; 2]>,
 | 
					    audio_on: Arc<[AtomicBool; 2]>,
 | 
				
			||||||
    linked: Arc<AtomicBool>,
 | 
					    linked: Arc<AtomicBool>,
 | 
				
			||||||
 | 
					    profilers: [Profiler; 2],
 | 
				
			||||||
    renderers: HashMap<SimId, TextureSink>,
 | 
					    renderers: HashMap<SimId, TextureSink>,
 | 
				
			||||||
    messages: HashMap<SimId, mpsc::Sender<Toast>>,
 | 
					    messages: HashMap<SimId, mpsc::Sender<Toast>>,
 | 
				
			||||||
    debuggers: HashMap<SimId, DebugInfo>,
 | 
					    debuggers: HashMap<SimId, DebugInfo>,
 | 
				
			||||||
| 
						 | 
					@ -164,6 +178,7 @@ impl Emulator {
 | 
				
			||||||
            state,
 | 
					            state,
 | 
				
			||||||
            audio_on,
 | 
					            audio_on,
 | 
				
			||||||
            linked,
 | 
					            linked,
 | 
				
			||||||
 | 
					            profilers: [Profiler::new(), Profiler::new()],
 | 
				
			||||||
            renderers: HashMap::new(),
 | 
					            renderers: HashMap::new(),
 | 
				
			||||||
            messages: HashMap::new(),
 | 
					            messages: HashMap::new(),
 | 
				
			||||||
            debuggers: HashMap::new(),
 | 
					            debuggers: HashMap::new(),
 | 
				
			||||||
| 
						 | 
					@ -213,6 +228,7 @@ impl Emulator {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let sim = &mut self.sims[index];
 | 
					        let sim = &mut self.sims[index];
 | 
				
			||||||
        sim.reset();
 | 
					        sim.reset();
 | 
				
			||||||
 | 
					        sim.monitor_events(self.profilers[sim_id.to_index()].is_enabled());
 | 
				
			||||||
        if let Some(cart) = new_cart {
 | 
					        if let Some(cart) = new_cart {
 | 
				
			||||||
            sim.load_cart(cart.rom.clone(), cart.sram.clone())?;
 | 
					            sim.load_cart(cart.rom.clone(), cart.sram.clone())?;
 | 
				
			||||||
            self.carts[index] = Some(cart);
 | 
					            self.carts[index] = Some(cart);
 | 
				
			||||||
| 
						 | 
					@ -383,6 +399,11 @@ impl Emulator {
 | 
				
			||||||
        self.watched_regions.insert(range, region);
 | 
					        self.watched_regions.insert(range, region);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn monitor_events(&mut self, sim_id: SimId) -> Result<()> {
 | 
				
			||||||
 | 
					        self.profilers[sim_id.to_index()].enable(true);
 | 
				
			||||||
 | 
					        self.reset_sim(sim_id, None)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn run(&mut self) {
 | 
					    pub fn run(&mut self) {
 | 
				
			||||||
        loop {
 | 
					        loop {
 | 
				
			||||||
            let idle = self.tick();
 | 
					            let idle = self.tick();
 | 
				
			||||||
| 
						 | 
					@ -438,12 +459,20 @@ impl Emulator {
 | 
				
			||||||
        let p1_running = running && p1_state == SimState::Ready;
 | 
					        let p1_running = running && p1_state == SimState::Ready;
 | 
				
			||||||
        let p2_running = running && p2_state == SimState::Ready;
 | 
					        let p2_running = running && p2_state == SimState::Ready;
 | 
				
			||||||
        let mut idle = !p1_running && !p2_running;
 | 
					        let mut idle = !p1_running && !p2_running;
 | 
				
			||||||
        if p1_running && p2_running {
 | 
					
 | 
				
			||||||
            Sim::emulate_many(&mut self.sims);
 | 
					        let cycles = self.emulate(p1_running, p2_running);
 | 
				
			||||||
        } else if p1_running {
 | 
					
 | 
				
			||||||
            self.sims[SimId::Player1.to_index()].emulate();
 | 
					        // if we're profiling, track events
 | 
				
			||||||
        } else if p2_running {
 | 
					        for ((sim, profiler), running) in self
 | 
				
			||||||
            self.sims[SimId::Player2.to_index()].emulate();
 | 
					            .sims
 | 
				
			||||||
 | 
					            .iter_mut()
 | 
				
			||||||
 | 
					            .zip(self.profilers.iter_mut())
 | 
				
			||||||
 | 
					            .zip([p1_running, p2_running])
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if !running || !profiler.is_enabled() {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            profiler.track(cycles, sim.take_sim_event());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if state == EmulatorState::Stepping {
 | 
					        if state == EmulatorState::Stepping {
 | 
				
			||||||
| 
						 | 
					@ -470,7 +499,7 @@ impl Emulator {
 | 
				
			||||||
                let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
 | 
					                let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                if let Some(reason) = sim.stop_reason() {
 | 
					                if let Some(reason) = sim.take_stop_reason() {
 | 
				
			||||||
                    let stop_reason = match reason {
 | 
					                    let stop_reason = match reason {
 | 
				
			||||||
                        StopReason::Stepped => DebugStopReason::Trace,
 | 
					                        StopReason::Stepped => DebugStopReason::Trace,
 | 
				
			||||||
                        StopReason::Watchpoint(watch, address) => {
 | 
					                        StopReason::Watchpoint(watch, address) => {
 | 
				
			||||||
| 
						 | 
					@ -529,6 +558,19 @@ impl Emulator {
 | 
				
			||||||
        idle
 | 
					        idle
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn emulate(&mut self, p1_running: bool, p2_running: bool) -> u32 {
 | 
				
			||||||
 | 
					        const MAX_CYCLES: u32 = 20_000_000;
 | 
				
			||||||
 | 
					        let mut cycles = MAX_CYCLES;
 | 
				
			||||||
 | 
					        if p1_running && p2_running {
 | 
				
			||||||
 | 
					            Sim::emulate_many(&mut self.sims, &mut cycles);
 | 
				
			||||||
 | 
					        } else if p1_running {
 | 
				
			||||||
 | 
					            self.sims[SimId::Player1.to_index()].emulate(&mut cycles);
 | 
				
			||||||
 | 
					        } else if p2_running {
 | 
				
			||||||
 | 
					            self.sims[SimId::Player2.to_index()].emulate(&mut cycles);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        MAX_CYCLES - cycles
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn handle_command(&mut self, command: EmulatorCommand) {
 | 
					    fn handle_command(&mut self, command: EmulatorCommand) {
 | 
				
			||||||
        match command {
 | 
					        match command {
 | 
				
			||||||
            EmulatorCommand::ConnectToSim(sim_id, renderer, messages) => {
 | 
					            EmulatorCommand::ConnectToSim(sim_id, renderer, messages) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					use crate::emulator::shrooms_vb_core::SimEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Profiler {
 | 
				
			||||||
 | 
					    enabled: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Profiler {
 | 
				
			||||||
 | 
					    pub fn new() -> Self {
 | 
				
			||||||
 | 
					        Self { enabled: false }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn enable(&mut self, enabled: bool) {
 | 
				
			||||||
 | 
					        self.enabled = enabled;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn is_enabled(&self) -> bool {
 | 
				
			||||||
 | 
					        self.enabled
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn track(&mut self, cycles: u32, event: Option<SimEvent>) {
 | 
				
			||||||
 | 
					        println!("profiler {cycles} {event:0x?}");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -71,8 +71,16 @@ pub enum VBWatchpointType {
 | 
				
			||||||
    Access,
 | 
					    Access,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type OnException = extern "C" fn(sim: *mut VB, cause: *mut u16) -> c_int;
 | 
				
			||||||
type OnExecute =
 | 
					type OnExecute =
 | 
				
			||||||
    extern "C" fn(sim: *mut VB, address: u32, code: *const u16, length: c_int) -> c_int;
 | 
					    extern "C" fn(sim: *mut VB, address: u32, code: *const u16, length: c_int) -> c_int;
 | 
				
			||||||
 | 
					type OnFetch = extern "C" fn(
 | 
				
			||||||
 | 
					    sim: *mut VB,
 | 
				
			||||||
 | 
					    fetch: c_int,
 | 
				
			||||||
 | 
					    address: u32,
 | 
				
			||||||
 | 
					    value: *mut i32,
 | 
				
			||||||
 | 
					    cycles: *mut u32,
 | 
				
			||||||
 | 
					) -> c_int;
 | 
				
			||||||
type OnFrame = extern "C" fn(sim: *mut VB) -> c_int;
 | 
					type OnFrame = extern "C" fn(sim: *mut VB) -> c_int;
 | 
				
			||||||
type OnRead = extern "C" fn(
 | 
					type OnRead = extern "C" fn(
 | 
				
			||||||
    sim: *mut VB,
 | 
					    sim: *mut VB,
 | 
				
			||||||
| 
						 | 
					@ -135,8 +143,15 @@ unsafe extern "C" {
 | 
				
			||||||
    fn vb_set_cart_ram(sim: *mut VB, sram: *mut c_void, size: u32) -> c_int;
 | 
					    fn vb_set_cart_ram(sim: *mut VB, sram: *mut c_void, size: u32) -> c_int;
 | 
				
			||||||
    #[link_name = "vbSetCartROM"]
 | 
					    #[link_name = "vbSetCartROM"]
 | 
				
			||||||
    fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int;
 | 
					    fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int;
 | 
				
			||||||
 | 
					    #[link_name = "vbSetExceptionCallback"]
 | 
				
			||||||
 | 
					    fn vb_set_exception_callback(
 | 
				
			||||||
 | 
					        sim: *mut VB,
 | 
				
			||||||
 | 
					        callback: Option<OnException>,
 | 
				
			||||||
 | 
					    ) -> Option<OnException>;
 | 
				
			||||||
    #[link_name = "vbSetExecuteCallback"]
 | 
					    #[link_name = "vbSetExecuteCallback"]
 | 
				
			||||||
    fn vb_set_execute_callback(sim: *mut VB, callback: Option<OnExecute>) -> Option<OnExecute>;
 | 
					    fn vb_set_execute_callback(sim: *mut VB, callback: Option<OnExecute>) -> Option<OnExecute>;
 | 
				
			||||||
 | 
					    #[link_name = "vbSetFetchCallback"]
 | 
				
			||||||
 | 
					    fn vb_set_fetch_callback(sim: *mut VB, callback: Option<OnFetch>) -> Option<OnFetch>;
 | 
				
			||||||
    #[link_name = "vbSetFrameCallback"]
 | 
					    #[link_name = "vbSetFrameCallback"]
 | 
				
			||||||
    fn vb_set_frame_callback(sim: *mut VB, callback: Option<OnFrame>) -> Option<OnFrame>;
 | 
					    fn vb_set_frame_callback(sim: *mut VB, callback: Option<OnFrame>) -> Option<OnFrame>;
 | 
				
			||||||
    #[link_name = "vbSetKeys"]
 | 
					    #[link_name = "vbSetKeys"]
 | 
				
			||||||
| 
						 | 
					@ -180,11 +195,21 @@ extern "C" fn on_frame(sim: *mut VB) -> c_int {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[unsafe(no_mangle)]
 | 
					#[unsafe(no_mangle)]
 | 
				
			||||||
extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length: c_int) -> c_int {
 | 
					extern "C" fn on_execute(sim: *mut VB, address: u32, code: *const u16, length: c_int) -> c_int {
 | 
				
			||||||
    // SAFETY: the *mut VB owns its userdata.
 | 
					    // SAFETY: the *mut VB owns its userdata.
 | 
				
			||||||
    // There is no way for the userdata to be null or otherwise invalid.
 | 
					    // There is no way for the userdata to be null or otherwise invalid.
 | 
				
			||||||
    let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
 | 
					    let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if data.monitor.enabled {
 | 
				
			||||||
 | 
					        // SAFETY: length is the length of code, in elements
 | 
				
			||||||
 | 
					        let code = unsafe { slice::from_raw_parts(code, length as usize) };
 | 
				
			||||||
 | 
					        if data.monitor.detect_event(sim, address, code) {
 | 
				
			||||||
 | 
					            // Something interesting will happen after this instruction is run.
 | 
				
			||||||
 | 
					            // The on_fetch callback will fire when it does.
 | 
				
			||||||
 | 
					            unsafe { vb_set_fetch_callback(sim, Some(on_fetch)) };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut stopped = data.stop_reason.is_some();
 | 
					    let mut stopped = data.stop_reason.is_some();
 | 
				
			||||||
    if data.step_from.is_some_and(|s| s != address) {
 | 
					    if data.step_from.is_some_and(|s| s != address) {
 | 
				
			||||||
        data.step_from = None;
 | 
					        data.step_from = None;
 | 
				
			||||||
| 
						 | 
					@ -199,6 +224,35 @@ extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length:
 | 
				
			||||||
    if stopped { 1 } else { 0 }
 | 
					    if stopped { 1 } else { 0 }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[unsafe(no_mangle)]
 | 
				
			||||||
 | 
					extern "C" fn on_fetch(
 | 
				
			||||||
 | 
					    sim: *mut VB,
 | 
				
			||||||
 | 
					    _fetch: c_int,
 | 
				
			||||||
 | 
					    _address: u32,
 | 
				
			||||||
 | 
					    _value: *mut i32,
 | 
				
			||||||
 | 
					    _cycles: *mut u32,
 | 
				
			||||||
 | 
					) -> c_int {
 | 
				
			||||||
 | 
					    // SAFETY: the *mut VB owns its userdata.
 | 
				
			||||||
 | 
					    // There is no way for the userdata to be null or otherwise invalid.
 | 
				
			||||||
 | 
					    let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
 | 
				
			||||||
 | 
					    data.monitor.event = data.monitor.queued_event.take();
 | 
				
			||||||
 | 
					    unsafe { vb_set_exception_callback(sim, Some(on_exception)) };
 | 
				
			||||||
 | 
					    unsafe { vb_set_fetch_callback(sim, None) };
 | 
				
			||||||
 | 
					    1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[unsafe(no_mangle)]
 | 
				
			||||||
 | 
					extern "C" fn on_exception(sim: *mut VB, cause: *mut u16) -> c_int {
 | 
				
			||||||
 | 
					    // SAFETY: the *mut VB owns its userdata.
 | 
				
			||||||
 | 
					    // There is no way for the userdata to be null or otherwise invalid.
 | 
				
			||||||
 | 
					    let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
 | 
				
			||||||
 | 
					    data.monitor.event = data.monitor.queued_event.take();
 | 
				
			||||||
 | 
					    data.monitor.queued_event = Some(SimEvent::Interrupt(unsafe { *cause }));
 | 
				
			||||||
 | 
					    unsafe { vb_set_exception_callback(sim, None) };
 | 
				
			||||||
 | 
					    unsafe { vb_set_fetch_callback(sim, Some(on_fetch)) };
 | 
				
			||||||
 | 
					    if data.monitor.event.is_some() { 1 } else { 0 }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[unsafe(no_mangle)]
 | 
					#[unsafe(no_mangle)]
 | 
				
			||||||
extern "C" fn on_read(
 | 
					extern "C" fn on_read(
 | 
				
			||||||
    sim: *mut VB,
 | 
					    sim: *mut VB,
 | 
				
			||||||
| 
						 | 
					@ -260,6 +314,83 @@ extern "C" fn on_write(
 | 
				
			||||||
    0
 | 
					    0
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[allow(dead_code)]
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub enum SimEvent {
 | 
				
			||||||
 | 
					    Call(u32),
 | 
				
			||||||
 | 
					    Return,
 | 
				
			||||||
 | 
					    Interrupt(u16),
 | 
				
			||||||
 | 
					    Reti,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct EventMonitor {
 | 
				
			||||||
 | 
					    enabled: bool,
 | 
				
			||||||
 | 
					    event: Option<SimEvent>,
 | 
				
			||||||
 | 
					    queued_event: Option<SimEvent>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl EventMonitor {
 | 
				
			||||||
 | 
					    fn new() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            enabled: false,
 | 
				
			||||||
 | 
					            event: None,
 | 
				
			||||||
 | 
					            queued_event: None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn detect_event(&mut self, sim: *mut VB, address: u32, code: &[u16]) -> bool {
 | 
				
			||||||
 | 
					        self.queued_event = self.do_detect_event(sim, address, code);
 | 
				
			||||||
 | 
					        self.queued_event.is_some()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn do_detect_event(&self, sim: *mut VB, address: u32, code: &[u16]) -> Option<SimEvent> {
 | 
				
			||||||
 | 
					        const JAL_OPCODE: u16 = 0b101011;
 | 
				
			||||||
 | 
					        const JMP_OPCODE: u16 = 0b000110;
 | 
				
			||||||
 | 
					        const RETI_OPCODE: u16 = 0b011001;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const fn format_i_reg_1(code: &[u16]) -> u8 {
 | 
				
			||||||
 | 
					            (code[0] & 0x1f) as u8
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const fn format_iv_disp(code: &[u16]) -> i32 {
 | 
				
			||||||
 | 
					            let value = ((code[0] & 0x3ff) as i32) << 16 | (code[1] as i32);
 | 
				
			||||||
 | 
					            value << 6 >> 6
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let opcode = code[0] >> 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if opcode == JAL_OPCODE {
 | 
				
			||||||
 | 
					            let disp = format_iv_disp(code);
 | 
				
			||||||
 | 
					            if disp != 4 {
 | 
				
			||||||
 | 
					                // JAL .+4 is how programs get r31 to a known value for indirect calls
 | 
				
			||||||
 | 
					                // (which we detect later.)
 | 
				
			||||||
 | 
					                // Any other JAL is a function call.
 | 
				
			||||||
 | 
					                return Some(SimEvent::Call(address.wrapping_add_signed(disp)));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if opcode == JMP_OPCODE {
 | 
				
			||||||
 | 
					            let jmp_reg = format_i_reg_1(code);
 | 
				
			||||||
 | 
					            if jmp_reg == 31 {
 | 
				
			||||||
 | 
					                // JMP[r31] is a return
 | 
				
			||||||
 | 
					                return Some(SimEvent::Return);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            let r31 = unsafe { vb_get_program_register(sim, 31) };
 | 
				
			||||||
 | 
					            if r31 as u32 == address.wrapping_add(2) {
 | 
				
			||||||
 | 
					                // JMP anywhere else, if r31 points to after the JMP, is an indirect call
 | 
				
			||||||
 | 
					                let target = unsafe { vb_get_program_register(sim, jmp_reg as u32) };
 | 
				
			||||||
 | 
					                return Some(SimEvent::Call(target as u32));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if opcode == RETI_OPCODE {
 | 
				
			||||||
 | 
					            return Some(SimEvent::Reti);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4;
 | 
					const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4;
 | 
				
			||||||
const AUDIO_CAPACITY_FLOATS: usize = AUDIO_CAPACITY_SAMPLES * 2;
 | 
					const AUDIO_CAPACITY_FLOATS: usize = AUDIO_CAPACITY_SAMPLES * 2;
 | 
				
			||||||
pub const EXPECTED_FRAME_SIZE: usize = 834 * 2;
 | 
					pub const EXPECTED_FRAME_SIZE: usize = 834 * 2;
 | 
				
			||||||
| 
						 | 
					@ -267,6 +398,7 @@ pub const EXPECTED_FRAME_SIZE: usize = 834 * 2;
 | 
				
			||||||
struct VBState {
 | 
					struct VBState {
 | 
				
			||||||
    frame_seen: bool,
 | 
					    frame_seen: bool,
 | 
				
			||||||
    stop_reason: Option<StopReason>,
 | 
					    stop_reason: Option<StopReason>,
 | 
				
			||||||
 | 
					    monitor: EventMonitor,
 | 
				
			||||||
    step_from: Option<u32>,
 | 
					    step_from: Option<u32>,
 | 
				
			||||||
    breakpoints: Vec<u32>,
 | 
					    breakpoints: Vec<u32>,
 | 
				
			||||||
    read_watchpoints: AddressSet,
 | 
					    read_watchpoints: AddressSet,
 | 
				
			||||||
| 
						 | 
					@ -277,6 +409,7 @@ struct VBState {
 | 
				
			||||||
impl VBState {
 | 
					impl VBState {
 | 
				
			||||||
    fn needs_execute_callback(&self) -> bool {
 | 
					    fn needs_execute_callback(&self) -> bool {
 | 
				
			||||||
        self.step_from.is_some()
 | 
					        self.step_from.is_some()
 | 
				
			||||||
 | 
					            || self.monitor.enabled
 | 
				
			||||||
            || !self.breakpoints.is_empty()
 | 
					            || !self.breakpoints.is_empty()
 | 
				
			||||||
            || !self.read_watchpoints.is_empty()
 | 
					            || !self.read_watchpoints.is_empty()
 | 
				
			||||||
            || !self.write_watchpoints.is_empty()
 | 
					            || !self.write_watchpoints.is_empty()
 | 
				
			||||||
| 
						 | 
					@ -311,6 +444,7 @@ impl Sim {
 | 
				
			||||||
        let state = VBState {
 | 
					        let state = VBState {
 | 
				
			||||||
            frame_seen: false,
 | 
					            frame_seen: false,
 | 
				
			||||||
            stop_reason: None,
 | 
					            stop_reason: None,
 | 
				
			||||||
 | 
					            monitor: EventMonitor::new(),
 | 
				
			||||||
            step_from: None,
 | 
					            step_from: None,
 | 
				
			||||||
            breakpoints: vec![],
 | 
					            breakpoints: vec![],
 | 
				
			||||||
            read_watchpoints: AddressSet::new(),
 | 
					            read_watchpoints: AddressSet::new(),
 | 
				
			||||||
| 
						 | 
					@ -332,6 +466,23 @@ impl Sim {
 | 
				
			||||||
        unsafe { vb_reset(self.sim) };
 | 
					        unsafe { vb_reset(self.sim) };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn monitor_events(&mut self, enabled: bool) {
 | 
				
			||||||
 | 
					        let state = self.get_state();
 | 
				
			||||||
 | 
					        state.monitor.enabled = enabled;
 | 
				
			||||||
 | 
					        state.monitor.event = None;
 | 
				
			||||||
 | 
					        state.monitor.queued_event = None;
 | 
				
			||||||
 | 
					        if enabled {
 | 
				
			||||||
 | 
					            unsafe { vb_set_execute_callback(self.sim, Some(on_execute)) };
 | 
				
			||||||
 | 
					            unsafe { vb_set_exception_callback(self.sim, Some(on_exception)) };
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            if !state.needs_execute_callback() {
 | 
				
			||||||
 | 
					                unsafe { vb_set_execute_callback(self.sim, None) };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            unsafe { vb_set_exception_callback(self.sim, None) };
 | 
				
			||||||
 | 
					            unsafe { vb_set_fetch_callback(self.sim, None) };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn load_cart(&mut self, mut rom: Vec<u8>, mut sram: Vec<u8>) -> Result<()> {
 | 
					    pub fn load_cart(&mut self, mut rom: Vec<u8>, mut sram: Vec<u8>) -> Result<()> {
 | 
				
			||||||
        self.unload_cart();
 | 
					        self.unload_cart();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -394,16 +545,14 @@ impl Sim {
 | 
				
			||||||
        unsafe { vb_set_peer(self.sim, ptr::null_mut()) };
 | 
					        unsafe { vb_set_peer(self.sim, ptr::null_mut()) };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn emulate(&mut self) {
 | 
					    pub fn emulate(&mut self, cycles: &mut u32) {
 | 
				
			||||||
        let mut cycles = 20_000_000;
 | 
					        unsafe { vb_emulate(self.sim, cycles) };
 | 
				
			||||||
        unsafe { vb_emulate(self.sim, &mut cycles) };
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn emulate_many(sims: &mut [Sim]) {
 | 
					    pub fn emulate_many(sims: &mut [Sim], cycles: &mut u32) {
 | 
				
			||||||
        let mut cycles = 20_000_000;
 | 
					 | 
				
			||||||
        let count = sims.len() as c_uint;
 | 
					        let count = sims.len() as c_uint;
 | 
				
			||||||
        let sims = sims.as_mut_ptr().cast();
 | 
					        let sims = sims.as_mut_ptr().cast();
 | 
				
			||||||
        unsafe { vb_emulate_ex(sims, count, &mut cycles) };
 | 
					        unsafe { vb_emulate_ex(sims, count, cycles) };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn read_pixels(&mut self, buffers: &mut [u8]) -> bool {
 | 
					    pub fn read_pixels(&mut self, buffers: &mut [u8]) -> bool {
 | 
				
			||||||
| 
						 | 
					@ -632,7 +781,7 @@ impl Sim {
 | 
				
			||||||
        Some(string)
 | 
					        Some(string)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn stop_reason(&mut self) -> Option<StopReason> {
 | 
					    pub fn take_stop_reason(&mut self) -> Option<StopReason> {
 | 
				
			||||||
        let data = self.get_state();
 | 
					        let data = self.get_state();
 | 
				
			||||||
        let reason = data.stop_reason.take();
 | 
					        let reason = data.stop_reason.take();
 | 
				
			||||||
        if !data.needs_execute_callback() {
 | 
					        if !data.needs_execute_callback() {
 | 
				
			||||||
| 
						 | 
					@ -641,6 +790,11 @@ impl Sim {
 | 
				
			||||||
        reason
 | 
					        reason
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn take_sim_event(&mut self) -> Option<SimEvent> {
 | 
				
			||||||
 | 
					        let data = self.get_state();
 | 
				
			||||||
 | 
					        data.monitor.event.take()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn get_state(&mut self) -> &mut VBState {
 | 
					    fn get_state(&mut self) -> &mut VBState {
 | 
				
			||||||
        // SAFETY: the *mut VB owns its userdata.
 | 
					        // SAFETY: the *mut VB owns its userdata.
 | 
				
			||||||
        // There is no way for the userdata to be null or otherwise invalid.
 | 
					        // There is no way for the userdata to be null or otherwise invalid.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,9 @@ struct Args {
 | 
				
			||||||
    /// Start a GDB/LLDB debug server on this port.
 | 
					    /// Start a GDB/LLDB debug server on this port.
 | 
				
			||||||
    #[arg(short, long)]
 | 
					    #[arg(short, long)]
 | 
				
			||||||
    debug_port: Option<u16>,
 | 
					    debug_port: Option<u16>,
 | 
				
			||||||
 | 
					    /// Enable profiling a game
 | 
				
			||||||
 | 
					    #[arg(short, long)]
 | 
				
			||||||
 | 
					    profile: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn init_logger() {
 | 
					fn init_logger() {
 | 
				
			||||||
| 
						 | 
					@ -106,6 +109,12 @@ fn main() -> Result<()> {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        builder = builder.start_paused(true);
 | 
					        builder = builder.start_paused(true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if args.profile {
 | 
				
			||||||
 | 
					        if args.rom.is_none() {
 | 
				
			||||||
 | 
					            bail!("to start profiling, please select a game.");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        builder = builder.monitor_events(true)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ThreadBuilder::default()
 | 
					    ThreadBuilder::default()
 | 
				
			||||||
        .name("Emulator".to_owned())
 | 
					        .name("Emulator".to_owned())
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue