Profiling #7
			
				
			
		
		
		
	
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -42,6 +42,7 @@ tokio = { version = "1", features = ["io-util", "macros", "net", "rt", "sync", "
 | 
			
		|||
tracing = { version = "0.1", features = ["release_max_level_info"] }
 | 
			
		||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
 | 
			
		||||
wgpu = "25"
 | 
			
		||||
wholesym = "0.8"
 | 
			
		||||
winit = { version = "0.30", features = ["serde"] }
 | 
			
		||||
 | 
			
		||||
[target.'cfg(windows)'.dependencies]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,12 +18,12 @@ use tracing::{error, warn};
 | 
			
		|||
 | 
			
		||||
use crate::{
 | 
			
		||||
    audio::Audio,
 | 
			
		||||
    emulator::{cart::Cart, shrooms_vb_core::SimEvent},
 | 
			
		||||
    emulator::cart::Cart,
 | 
			
		||||
    graphics::TextureSink,
 | 
			
		||||
    memory::{MemoryRange, MemoryRegion},
 | 
			
		||||
};
 | 
			
		||||
use shrooms_vb_core::{EXPECTED_FRAME_SIZE, Sim, StopReason};
 | 
			
		||||
pub use shrooms_vb_core::{VBKey, VBRegister, VBWatchpointType};
 | 
			
		||||
pub use shrooms_vb_core::{SimEvent, VBKey, VBRegister, VBWatchpointType};
 | 
			
		||||
 | 
			
		||||
mod address_set;
 | 
			
		||||
mod cart;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										103
									
								
								src/profiler.rs
								
								
								
								
							
							
						
						
									
										103
									
								
								src/profiler.rs
								
								
								
								
							| 
						 | 
				
			
			@ -1,4 +1,6 @@
 | 
			
		|||
use std::{
 | 
			
		||||
    collections::HashMap,
 | 
			
		||||
    path::PathBuf,
 | 
			
		||||
    sync::{
 | 
			
		||||
        Arc,
 | 
			
		||||
        atomic::{AtomicBool, Ordering},
 | 
			
		||||
| 
						 | 
				
			
			@ -7,8 +9,9 @@ use std::{
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
use tokio::{select, sync::mpsc};
 | 
			
		||||
use wholesym::{SymbolManager, SymbolMap};
 | 
			
		||||
 | 
			
		||||
use crate::emulator::{EmulatorClient, EmulatorCommand, ProfileEvent, SimId};
 | 
			
		||||
use crate::emulator::{EmulatorClient, EmulatorCommand, ProfileEvent, SimEvent, SimId};
 | 
			
		||||
 | 
			
		||||
pub struct Profiler {
 | 
			
		||||
    sim_id: SimId,
 | 
			
		||||
| 
						 | 
				
			
			@ -65,15 +68,109 @@ async fn run_profile(sim_id: SimId, client: EmulatorClient, running: Arc<AtomicB
 | 
			
		|||
    client.send_command(EmulatorCommand::StartProfiling(sim_id, profile_sync));
 | 
			
		||||
 | 
			
		||||
    running.store(true, Ordering::Relaxed);
 | 
			
		||||
 | 
			
		||||
    let mut session = None;
 | 
			
		||||
    while let Some(event) = profile_source.recv().await {
 | 
			
		||||
        match event {
 | 
			
		||||
            ProfileEvent::Start { file_path } => {
 | 
			
		||||
                println!("profiling {}", file_path.display());
 | 
			
		||||
                session = Some(ProfileSession::new(file_path).await);
 | 
			
		||||
            }
 | 
			
		||||
            ProfileEvent::Update { cycles, event } => {
 | 
			
		||||
                println!("update {cycles} {event:#x?}");
 | 
			
		||||
                if let Some(session) = &mut session {
 | 
			
		||||
                    session.track_elapsed_cycles(cycles);
 | 
			
		||||
                    if let Some(event) = event {
 | 
			
		||||
                        session.track_event(event);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    running.store(false, Ordering::Release);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ProfileSession {
 | 
			
		||||
    symbol_map: SymbolMap,
 | 
			
		||||
    call_stacks: HashMap<u16, Vec<StackFrame>>,
 | 
			
		||||
    context_stack: Vec<u16>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct StackFrame {
 | 
			
		||||
    #[expect(dead_code)]
 | 
			
		||||
    address: Option<u32>,
 | 
			
		||||
    cycles: u64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ProfileSession {
 | 
			
		||||
    async fn new(file_path: PathBuf) -> Self {
 | 
			
		||||
        let symbol_manager = SymbolManager::with_config(Default::default());
 | 
			
		||||
        let symbol_map = symbol_manager
 | 
			
		||||
            .load_symbol_map_for_binary_at_path(&file_path, None)
 | 
			
		||||
            .await
 | 
			
		||||
            .expect("cannae load symbols");
 | 
			
		||||
        let mut call_stacks = HashMap::new();
 | 
			
		||||
        call_stacks.insert(
 | 
			
		||||
            0,
 | 
			
		||||
            vec![StackFrame {
 | 
			
		||||
                address: None,
 | 
			
		||||
                cycles: 0,
 | 
			
		||||
            }],
 | 
			
		||||
        );
 | 
			
		||||
        Self {
 | 
			
		||||
            symbol_map,
 | 
			
		||||
            call_stacks,
 | 
			
		||||
            context_stack: vec![],
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 {
 | 
			
		||||
            panic!("missing stack {code:04x}");
 | 
			
		||||
        };
 | 
			
		||||
        for frame in stack {
 | 
			
		||||
            frame.cycles += cycles as u64;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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");
 | 
			
		||||
                };
 | 
			
		||||
                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 {
 | 
			
		||||
                    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,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            SimEvent::Return => {
 | 
			
		||||
                let code = self.context_stack.last().copied().unwrap_or(0);
 | 
			
		||||
                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");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue