diff --git a/src/emulator/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs index 212d10c..0edd327 100644 --- a/src/emulator/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -210,7 +210,7 @@ extern "C" fn on_execute(sim: *mut VB, address: u32, code: *const u16, length: c } } - let mut stopped = data.stop_reason.is_some(); + let mut stopped = data.stop_reason.is_some() || data.monitor.event.is_some(); if data.step_from.is_some_and(|s| s != address) { data.step_from = None; data.stop_reason = Some(StopReason::Stepped); @@ -247,7 +247,13 @@ extern "C" fn on_exception(sim: *mut VB, cause: *mut u16) -> c_int { // 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 })); + let cause = unsafe { *cause }; + let pc = if cause == 0xff70 { + 0xffffff60 + } else { + (cause & 0xfff0) as u32 | 0xffff0000 + }; + data.monitor.queued_event = Some(SimEvent::Interrupt(cause, pc)); 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 } @@ -319,7 +325,8 @@ extern "C" fn on_write( pub enum SimEvent { Call(u32), Return, - Interrupt(u16), + Halt, + Interrupt(u16, u32), Reti, } @@ -327,6 +334,7 @@ struct EventMonitor { enabled: bool, event: Option, queued_event: Option, + just_halted: bool, } impl EventMonitor { @@ -335,6 +343,7 @@ impl EventMonitor { enabled: false, event: None, queued_event: None, + just_halted: false, } } @@ -343,7 +352,8 @@ impl EventMonitor { self.queued_event.is_some() } - fn do_detect_event(&self, sim: *mut VB, address: u32, code: &[u16]) -> Option { + fn do_detect_event(&mut self, sim: *mut VB, address: u32, code: &[u16]) -> Option { + const HALT_OPCODE: u16 = 0b011010; const JAL_OPCODE: u16 = 0b101011; const JMP_OPCODE: u16 = 0b000110; const RETI_OPCODE: u16 = 0b011001; @@ -359,6 +369,18 @@ impl EventMonitor { let opcode = code[0] >> 10; + if opcode == HALT_OPCODE { + if !self.just_halted { + self.just_halted = true; + self.event = Some(SimEvent::Halt); + } else { + self.just_halted = false; + } + // Don't _return_ an event, we want to emit this right away. + // If the CPU is halting, no other callbacks will run for a long time. + return None; + } + if opcode == JAL_OPCODE { let disp = format_iv_disp(code); if disp != 4 { diff --git a/src/profiler.rs b/src/profiler.rs index 20195a8..dd27a47 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -96,10 +96,11 @@ struct ProfileSession { struct StackFrame { #[expect(dead_code)] - address: Option, + address: u32, cycles: u64, } +const RESET_CODE: u16 = 0xfff0; impl ProfileSession { async fn new(file_path: PathBuf) -> Self { let symbol_manager = SymbolManager::with_config(Default::default()); @@ -109,22 +110,24 @@ impl ProfileSession { .expect("cannae load symbols"); let mut call_stacks = HashMap::new(); call_stacks.insert( - 0, + RESET_CODE, vec![StackFrame { - address: None, + address: 0xfffffff0, cycles: 0, }], ); Self { symbol_map, call_stacks, - context_stack: vec![], + context_stack: vec![RESET_CODE], } } fn track_elapsed_cycles(&mut self, cycles: u32) { - let code = self.context_stack.last().copied().unwrap_or(0); - let Some(stack) = self.call_stacks.get_mut(&code) else { + let Some(code) = self.context_stack.last() else { + return; // program is halted, CPU is idle + }; + let Some(stack) = self.call_stacks.get_mut(code) else { panic!("missing stack {code:04x}"); }; for frame in stack { @@ -134,42 +137,63 @@ impl ProfileSession { fn track_event(&mut self, event: SimEvent) { match event { - SimEvent::Interrupt(code) => { - self.context_stack.push(code); - if self.call_stacks.insert(code, vec![]).is_some() { - panic!("{code:04x} fired twice"); - } - } - SimEvent::Reti => { - let Some(code) = self.context_stack.pop() else { - panic!("reti when not in interrupt"); + SimEvent::Call(address) => { + let Some(code) = self.context_stack.last() else { + panic!("How did we call anything when we're halted?"); }; - if self.call_stacks.remove(&code).is_none() { - panic!("{code:04x} popped but never called") - } - } - SimEvent::Call(addr) => { - let code = self.context_stack.last().copied().unwrap_or(0); - let Some(stack) = self.call_stacks.get_mut(&code) else { + let Some(stack) = self.call_stacks.get_mut(code) else { panic!("missing stack {code:04x}"); }; let name = self .symbol_map - .lookup_sync(wholesym::LookupAddress::Svma(addr as u64)); - println!("depth {}: {:?}", stack.len(), name); - stack.push(StackFrame { - address: Some(addr), - cycles: 0, - }); + .lookup_sync(wholesym::LookupAddress::Svma(address as u64)); + println!("depth {}: {:x?}", stack.len(), name); + stack.push(StackFrame { address, cycles: 0 }); } SimEvent::Return => { - let code = self.context_stack.last().copied().unwrap_or(0); - let Some(stack) = self.call_stacks.get_mut(&code) else { + let Some(code) = self.context_stack.last() else { + panic!("how did we return when we're halted?"); + }; + let Some(stack) = self.call_stacks.get_mut(code) else { panic!("missing stack {code:04x}"); }; if stack.pop().is_none() { panic!("returned from {code:04x} but stack was empty"); } + if stack.is_empty() { + panic!("returned to oblivion"); + } + } + SimEvent::Halt => { + let Some(RESET_CODE) = self.context_stack.pop() else { + panic!("halted when not in an interrupt"); + }; + } + SimEvent::Interrupt(code, address) => { + // if the CPU was halted before, wake it up now + if self.context_stack.is_empty() { + self.context_stack.push(RESET_CODE); + } + + self.context_stack.push(code); + if self + .call_stacks + .insert(code, vec![StackFrame { address, cycles: 0 }]) + .is_some() + { + panic!("{code:04x} fired twice"); + } + } + SimEvent::Reti => { + let Some(code) = self.context_stack.pop() else { + panic!("RETI when halted"); + }; + if code == RESET_CODE { + panic!("RETI when not in interrupt"); + } + if self.call_stacks.remove(&code).is_none() { + panic!("{code:04x} popped but never called"); + } } } }