Profiling #7
|
@ -35,7 +35,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
"gimli",
|
"gimli 0.31.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1620,6 +1620,17 @@ dependencies = [
|
||||||
"stable_deref_trait",
|
"stable_deref_trait",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.32.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc6298e594375a7fead9efd5568f0a46e6a154fb6a9bdcbe3c06946ffd81a5f6"
|
||||||
|
dependencies = [
|
||||||
|
"fallible-iterator",
|
||||||
|
"indexmap",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gl_generator"
|
name = "gl_generator"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
@ -2173,6 +2184,7 @@ dependencies = [
|
||||||
"fixed",
|
"fixed",
|
||||||
"fxprof-processed-profile",
|
"fxprof-processed-profile",
|
||||||
"gilrs",
|
"gilrs",
|
||||||
|
"gimli 0.32.2",
|
||||||
"hex",
|
"hex",
|
||||||
"image",
|
"image",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
|
@ -3868,7 +3880,7 @@ dependencies = [
|
||||||
"debugid",
|
"debugid",
|
||||||
"elsa",
|
"elsa",
|
||||||
"flate2",
|
"flate2",
|
||||||
"gimli",
|
"gimli 0.31.1",
|
||||||
"linux-perf-data",
|
"linux-perf-data",
|
||||||
"lzma-rs",
|
"lzma-rs",
|
||||||
"macho-unwind-info",
|
"macho-unwind-info",
|
||||||
|
|
|
@ -24,6 +24,7 @@ egui-wgpu = { version = "0.32", features = ["winit"] }
|
||||||
fxprof-processed-profile = "0.8"
|
fxprof-processed-profile = "0.8"
|
||||||
fixed = { version = "1.28", features = ["num-traits"] }
|
fixed = { version = "1.28", features = ["num-traits"] }
|
||||||
gilrs = { version = "0.11", features = ["serde-serialize"] }
|
gilrs = { version = "0.11", features = ["serde-serialize"] }
|
||||||
|
gimli = "0.32"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
|
|
|
@ -18,17 +18,20 @@ use tracing::{error, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
audio::Audio,
|
audio::Audio,
|
||||||
emulator::cart::Cart,
|
|
||||||
graphics::TextureSink,
|
graphics::TextureSink,
|
||||||
memory::{MemoryRange, MemoryRegion},
|
memory::{MemoryRange, MemoryRegion},
|
||||||
};
|
};
|
||||||
|
use cart::Cart;
|
||||||
pub use game_info::GameInfo;
|
pub use game_info::GameInfo;
|
||||||
|
pub use inline_stack_map::InlineStack;
|
||||||
|
use inline_stack_map::InlineStackMap;
|
||||||
use shrooms_vb_core::{EXPECTED_FRAME_SIZE, Sim, StopReason};
|
use shrooms_vb_core::{EXPECTED_FRAME_SIZE, Sim, StopReason};
|
||||||
pub use shrooms_vb_core::{SimEvent, VBKey, VBRegister, VBWatchpointType};
|
pub use shrooms_vb_core::{SimEvent, VBKey, VBRegister, VBWatchpointType};
|
||||||
|
|
||||||
mod address_set;
|
mod address_set;
|
||||||
mod cart;
|
mod cart;
|
||||||
mod game_info;
|
mod game_info;
|
||||||
|
mod inline_stack_map;
|
||||||
mod shrooms_vb_core;
|
mod shrooms_vb_core;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||||
|
@ -231,11 +234,11 @@ impl Emulator {
|
||||||
})
|
})
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
sim.monitor_events(true);
|
sim.monitor_events(true, cart.info.inline_stack_map().clone());
|
||||||
profiling = true;
|
profiling = true;
|
||||||
}
|
}
|
||||||
if !profiling {
|
if !profiling {
|
||||||
sim.monitor_events(false);
|
sim.monitor_events(false, InlineStackMap::empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.sim_state[index].load(Ordering::Acquire) == SimState::Ready {
|
if self.sim_state[index].load(Ordering::Acquire) == SimState::Ready {
|
||||||
|
@ -476,15 +479,18 @@ impl Emulator {
|
||||||
if !running {
|
if !running {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(p) = profiler
|
if let Some(p) = profiler {
|
||||||
&& p.send(ProfileEvent::Update {
|
let (event, inline_stack) = sim.take_profiler_updates();
|
||||||
|
if p.send(ProfileEvent::Update {
|
||||||
cycles,
|
cycles,
|
||||||
event: sim.take_sim_event(),
|
event,
|
||||||
|
inline_stack,
|
||||||
})
|
})
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
sim.monitor_events(false);
|
sim.monitor_events(false, InlineStackMap::empty());
|
||||||
*profiler = None;
|
*profiler = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -842,6 +848,7 @@ pub enum ProfileEvent {
|
||||||
Update {
|
Update {
|
||||||
cycles: u32,
|
cycles: u32,
|
||||||
event: Option<SimEvent>,
|
event: Option<SimEvent>,
|
||||||
|
inline_stack: Option<InlineStack>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
use std::{path::Path, sync::Arc};
|
use std::{borrow::Cow, path::Path, sync::Arc};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Result, bail};
|
||||||
use fxprof_processed_profile::{LibraryInfo, Symbol, SymbolTable, debugid::DebugId};
|
use fxprof_processed_profile::{LibraryInfo, Symbol, SymbolTable, debugid::DebugId};
|
||||||
use object::{Object, ObjectSymbol};
|
use object::{Object, ObjectSection, ObjectSymbol};
|
||||||
use wholesym::samply_symbols::{DebugIdExt, demangle_any};
|
use wholesym::samply_symbols::{DebugIdExt, demangle_any};
|
||||||
|
|
||||||
|
use crate::emulator::inline_stack_map::{InlineStackMap, InlineStackMapBuilder};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GameInfo {
|
pub struct GameInfo {
|
||||||
library_info: LibraryInfo,
|
library_info: LibraryInfo,
|
||||||
|
inline_stack_map: InlineStackMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameInfo {
|
impl GameInfo {
|
||||||
|
@ -29,6 +32,9 @@ impl GameInfo {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let inline_stack_map =
|
||||||
|
build_inline_stack_map(file).unwrap_or_else(|_| InlineStackMap::empty());
|
||||||
|
|
||||||
let library_info = LibraryInfo {
|
let library_info = LibraryInfo {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
debug_name: name,
|
debug_name: name,
|
||||||
|
@ -40,7 +46,10 @@ impl GameInfo {
|
||||||
symbol_table: Some(Arc::new(SymbolTable::new(symbols))),
|
symbol_table: Some(Arc::new(SymbolTable::new(symbols))),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self { library_info })
|
Ok(Self {
|
||||||
|
library_info,
|
||||||
|
inline_stack_map,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn empty(file_path: &Path) -> Self {
|
pub fn empty(file_path: &Path) -> Self {
|
||||||
|
@ -55,7 +64,11 @@ impl GameInfo {
|
||||||
arch: None,
|
arch: None,
|
||||||
symbol_table: None,
|
symbol_table: None,
|
||||||
};
|
};
|
||||||
Self { library_info }
|
let inline_stack_map = InlineStackMap::empty();
|
||||||
|
Self {
|
||||||
|
library_info,
|
||||||
|
inline_stack_map,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
|
@ -65,6 +78,111 @@ impl GameInfo {
|
||||||
pub fn library_info(&self) -> &LibraryInfo {
|
pub fn library_info(&self) -> &LibraryInfo {
|
||||||
&self.library_info
|
&self.library_info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inline_stack_map(&self) -> &InlineStackMap {
|
||||||
|
&self.inline_stack_map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_inline_stack_map(file: object::File) -> Result<InlineStackMap> {
|
||||||
|
let endian = if file.is_little_endian() {
|
||||||
|
gimli::RunTimeEndian::Little
|
||||||
|
} else {
|
||||||
|
gimli::RunTimeEndian::Big
|
||||||
|
};
|
||||||
|
fn load_section<'a>(file: &'a object::File, id: gimli::SectionId) -> Result<Cow<'a, [u8]>> {
|
||||||
|
let input = match file.section_by_name(id.name()) {
|
||||||
|
Some(section) => section.uncompressed_data()?,
|
||||||
|
None => Cow::Owned(vec![]),
|
||||||
|
};
|
||||||
|
Ok(input)
|
||||||
|
}
|
||||||
|
let dorf = gimli::DwarfSections::load(|id| load_section(&file, id))?;
|
||||||
|
let dorf = dorf.borrow(|sec| gimli::EndianSlice::new(sec, endian));
|
||||||
|
let mut units = dorf.units();
|
||||||
|
let mut frames = InlineStackMap::builder();
|
||||||
|
while let Some(header) = units.next()? {
|
||||||
|
let unit = dorf.unit(header)?;
|
||||||
|
let mut entree = unit.entries_tree(None)?;
|
||||||
|
let root = entree.root()?;
|
||||||
|
let mut ctx = ParseContext {
|
||||||
|
dorf: &dorf,
|
||||||
|
unit: &unit,
|
||||||
|
frames: &mut frames,
|
||||||
|
};
|
||||||
|
parse_inline(&mut ctx, root)?;
|
||||||
|
}
|
||||||
|
Ok(frames.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reader<'a> = gimli::EndianSlice<'a, gimli::RunTimeEndian>;
|
||||||
|
|
||||||
|
struct ParseContext<'a> {
|
||||||
|
dorf: &'a gimli::Dwarf<Reader<'a>>,
|
||||||
|
unit: &'a gimli::Unit<Reader<'a>>,
|
||||||
|
frames: &'a mut InlineStackMapBuilder,
|
||||||
|
}
|
||||||
|
impl ParseContext<'_> {
|
||||||
|
fn name_attr(&self, attr: gimli::AttributeValue<Reader>) -> Result<Option<String>> {
|
||||||
|
match attr {
|
||||||
|
gimli::AttributeValue::DebugInfoRef(offset) => {
|
||||||
|
let mut units = self.dorf.units();
|
||||||
|
while let Some(header) = units.next()? {
|
||||||
|
if let Some(offset) = offset.to_unit_offset(&header) {
|
||||||
|
let unit = self.dorf.unit(header)?;
|
||||||
|
return self.name_entry(&unit, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
gimli::AttributeValue::UnitRef(offset) => self.name_entry(self.unit, offset),
|
||||||
|
other => {
|
||||||
|
bail!("unrecognized attr {other:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_entry(
|
||||||
|
&self,
|
||||||
|
unit: &gimli::Unit<Reader>,
|
||||||
|
offset: gimli::UnitOffset,
|
||||||
|
) -> Result<Option<String>> {
|
||||||
|
let abbreviations = self.dorf.abbreviations(&unit.header)?;
|
||||||
|
let mut entries = unit.header.entries_raw(&abbreviations, Some(offset))?;
|
||||||
|
let Some(abbrev) = entries.read_abbreviation()? else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let mut name = None;
|
||||||
|
for spec in abbrev.attributes() {
|
||||||
|
let attr = entries.read_attribute(*spec)?;
|
||||||
|
if attr.name() == gimli::DW_AT_linkage_name
|
||||||
|
|| (attr.name() == gimli::DW_AT_name && name.is_none())
|
||||||
|
{
|
||||||
|
name = Some(self.dorf.attr_string(unit, attr.value())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(name.map(|n| demangle_any(&String::from_utf8_lossy(&n))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_inline(ctx: &mut ParseContext, node: gimli::EntriesTreeNode<Reader>) -> Result<()> {
|
||||||
|
if node.entry().tag() == gimli::DW_TAG_inlined_subroutine
|
||||||
|
&& let Some(attr) = node.entry().attr_value(gimli::DW_AT_abstract_origin)?
|
||||||
|
&& let Some(name) = ctx.name_attr(attr)?
|
||||||
|
{
|
||||||
|
let name = Arc::new(name);
|
||||||
|
let mut ranges = ctx.dorf.die_ranges(ctx.unit, node.entry())?;
|
||||||
|
while let Some(range) = ranges.next()? {
|
||||||
|
let start = range.begin as u32;
|
||||||
|
let end = range.end as u32;
|
||||||
|
ctx.frames.add(start, end, name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut children = node.children();
|
||||||
|
while let Some(child) = children.next()? {
|
||||||
|
parse_inline(ctx, child)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name_and_path(file_path: &Path) -> (String, String) {
|
fn name_and_path(file_path: &Path) -> (String, String) {
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type InlineStack = Arc<Vec<Arc<String>>>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct InlineStackMap {
|
||||||
|
entries: Vec<(u32, InlineStack)>,
|
||||||
|
empty: InlineStack,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlineStackMap {
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
entries: vec![],
|
||||||
|
empty: Arc::new(vec![]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn builder() -> InlineStackMapBuilder {
|
||||||
|
InlineStackMapBuilder {
|
||||||
|
events: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get(&self, address: u32) -> &InlineStack {
|
||||||
|
match self.entries.binary_search_by_key(&address, |(a, _)| *a) {
|
||||||
|
Ok(index) => self.entries.get(index),
|
||||||
|
Err(after) => after.checked_sub(1).and_then(|i| self.entries.get(i)),
|
||||||
|
}
|
||||||
|
.map(|(_, s)| s)
|
||||||
|
.unwrap_or(&self.empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Event {
|
||||||
|
end: usize,
|
||||||
|
start: Vec<Arc<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InlineStackMapBuilder {
|
||||||
|
events: BTreeMap<u32, Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlineStackMapBuilder {
|
||||||
|
pub fn add(&mut self, start: u32, end: u32, name: Arc<String>) {
|
||||||
|
self.events.entry(start).or_default().start.push(name);
|
||||||
|
self.events.entry(end).or_default().end += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> InlineStackMap {
|
||||||
|
let empty = Arc::new(vec![]);
|
||||||
|
let mut entries = vec![];
|
||||||
|
let mut stack_indexes = vec![];
|
||||||
|
let mut stack = vec![];
|
||||||
|
let mut string_dedup = HashMap::new();
|
||||||
|
let mut stack_dedup = BTreeMap::new();
|
||||||
|
stack_dedup.insert(vec![], empty.clone());
|
||||||
|
|
||||||
|
for (address, event) in self.events {
|
||||||
|
for _ in 0..event.end {
|
||||||
|
stack.pop();
|
||||||
|
stack_indexes.pop();
|
||||||
|
}
|
||||||
|
for call in event.start {
|
||||||
|
if let Some(index) = string_dedup.get(&call) {
|
||||||
|
stack.push(call);
|
||||||
|
stack_indexes.push(*index);
|
||||||
|
} else {
|
||||||
|
let index = string_dedup.len();
|
||||||
|
string_dedup.insert(call.clone(), index);
|
||||||
|
stack.push(call);
|
||||||
|
stack_indexes.push(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(stack) = stack_dedup.get(&stack_indexes) {
|
||||||
|
entries.push((address, stack.clone()));
|
||||||
|
} else {
|
||||||
|
let stack = Arc::new(stack.clone());
|
||||||
|
stack_dedup.insert(stack_indexes.clone(), stack.clone());
|
||||||
|
entries.push((address, stack));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InlineStackMap { entries, empty }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
use std::{borrow::Cow, ffi::c_void, ptr, slice};
|
use std::{borrow::Cow, ffi::c_void, ptr, slice, sync::Arc};
|
||||||
|
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use num_derive::{FromPrimitive, ToPrimitive};
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::emulator::inline_stack_map::{InlineStack, InlineStackMap};
|
||||||
|
|
||||||
use super::address_set::AddressSet;
|
use super::address_set::AddressSet;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -206,11 +208,9 @@ extern "C" fn on_execute(sim: *mut VB, address: u32, code: *const u16, length: c
|
||||||
if data.monitor.enabled {
|
if data.monitor.enabled {
|
||||||
// SAFETY: length is the length of code, in elements
|
// SAFETY: length is the length of code, in elements
|
||||||
let code = unsafe { slice::from_raw_parts(code, length as usize) };
|
let code = unsafe { slice::from_raw_parts(code, length as usize) };
|
||||||
if data.monitor.detect_event(sim, address, code) {
|
data.monitor.detect_event(sim, address, code);
|
||||||
// Something interesting will happen after this instruction is run.
|
// Something interesting will happen after this instruction is run.
|
||||||
// The on_fetch callback will fire when it does.
|
// We'll react in the on_fetch callback it does.
|
||||||
unsafe { vb_set_fetch_callback(sim, Some(on_fetch)) };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut stopped = data.stop_reason.is_some() || data.monitor.event.is_some();
|
let mut stopped = data.stop_reason.is_some() || data.monitor.event.is_some();
|
||||||
|
@ -231,7 +231,7 @@ extern "C" fn on_execute(sim: *mut VB, address: u32, code: *const u16, length: c
|
||||||
extern "C" fn on_fetch(
|
extern "C" fn on_fetch(
|
||||||
sim: *mut VB,
|
sim: *mut VB,
|
||||||
_fetch: c_int,
|
_fetch: c_int,
|
||||||
_address: u32,
|
address: u32,
|
||||||
_value: *mut i32,
|
_value: *mut i32,
|
||||||
_cycles: *mut u32,
|
_cycles: *mut u32,
|
||||||
) -> c_int {
|
) -> c_int {
|
||||||
|
@ -239,9 +239,13 @@ extern "C" fn on_fetch(
|
||||||
// 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() };
|
||||||
data.monitor.event = data.monitor.queued_event.take();
|
data.monitor.event = data.monitor.queued_event.take();
|
||||||
|
data.monitor.new_inline_stack = data.monitor.detect_new_inline_stack(address);
|
||||||
unsafe { vb_set_exception_callback(sim, Some(on_exception)) };
|
unsafe { vb_set_exception_callback(sim, Some(on_exception)) };
|
||||||
unsafe { vb_set_fetch_callback(sim, None) };
|
if data.monitor.event.is_some() || data.monitor.new_inline_stack.is_some() {
|
||||||
1
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
|
@ -249,17 +253,21 @@ extern "C" fn on_exception(sim: *mut VB, cause: *mut u16) -> 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() };
|
||||||
data.monitor.event = data.monitor.queued_event.take();
|
|
||||||
let cause = unsafe { *cause };
|
let cause = unsafe { *cause };
|
||||||
let pc = if cause == 0xff70 {
|
let pc = if cause == 0xff70 {
|
||||||
0xffffff60
|
0xffffff60
|
||||||
} else {
|
} else {
|
||||||
(cause & 0xfff0) as u32 | 0xffff0000
|
(cause & 0xfff0) as u32 | 0xffff0000
|
||||||
};
|
};
|
||||||
|
data.monitor.event = data.monitor.queued_event.take();
|
||||||
|
data.monitor.new_inline_stack = data.monitor.detect_new_inline_stack(pc);
|
||||||
data.monitor.queued_event = Some(SimEvent::Interrupt(cause, pc));
|
data.monitor.queued_event = Some(SimEvent::Interrupt(cause, pc));
|
||||||
unsafe { vb_set_exception_callback(sim, None) };
|
unsafe { vb_set_exception_callback(sim, None) };
|
||||||
unsafe { vb_set_fetch_callback(sim, Some(on_fetch)) };
|
if data.monitor.event.is_some() || data.monitor.new_inline_stack.is_some() {
|
||||||
if data.monitor.event.is_some() { 1 } else { 0 }
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
|
@ -339,18 +347,35 @@ struct EventMonitor {
|
||||||
event: Option<SimEvent>,
|
event: Option<SimEvent>,
|
||||||
queued_event: Option<SimEvent>,
|
queued_event: Option<SimEvent>,
|
||||||
just_halted: bool,
|
just_halted: bool,
|
||||||
|
inline_stack_map: InlineStackMap,
|
||||||
|
new_inline_stack: Option<InlineStack>,
|
||||||
|
last_inline_stack: InlineStack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventMonitor {
|
impl EventMonitor {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
|
let inline_stack_map = InlineStackMap::empty();
|
||||||
|
let last_inline_stack = inline_stack_map.get(0).clone();
|
||||||
Self {
|
Self {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
event: None,
|
event: None,
|
||||||
queued_event: None,
|
queued_event: None,
|
||||||
just_halted: false,
|
just_halted: false,
|
||||||
|
inline_stack_map,
|
||||||
|
new_inline_stack: None,
|
||||||
|
last_inline_stack,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn detect_new_inline_stack(&mut self, address: u32) -> Option<InlineStack> {
|
||||||
|
let stack = self.inline_stack_map.get(address);
|
||||||
|
if Arc::ptr_eq(stack, &self.last_inline_stack) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.last_inline_stack = stack.clone();
|
||||||
|
Some(stack.clone())
|
||||||
|
}
|
||||||
|
|
||||||
fn detect_event(&mut self, sim: *mut VB, address: u32, code: &[u16]) -> bool {
|
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 = self.do_detect_event(sim, address, code);
|
||||||
self.queued_event.is_some()
|
self.queued_event.is_some()
|
||||||
|
@ -492,14 +517,18 @@ impl Sim {
|
||||||
unsafe { vb_reset(self.sim) };
|
unsafe { vb_reset(self.sim) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn monitor_events(&mut self, enabled: bool) {
|
pub fn monitor_events(&mut self, enabled: bool, inline_stack_map: InlineStackMap) {
|
||||||
let state = self.get_state();
|
let state = self.get_state();
|
||||||
state.monitor.enabled = enabled;
|
state.monitor.enabled = enabled;
|
||||||
state.monitor.event = None;
|
state.monitor.event = None;
|
||||||
state.monitor.queued_event = None;
|
state.monitor.queued_event = None;
|
||||||
|
state.monitor.new_inline_stack = None;
|
||||||
|
state.monitor.last_inline_stack = inline_stack_map.get(0).clone();
|
||||||
|
state.monitor.inline_stack_map = inline_stack_map;
|
||||||
if enabled {
|
if enabled {
|
||||||
unsafe { vb_set_execute_callback(self.sim, Some(on_execute)) };
|
unsafe { vb_set_execute_callback(self.sim, Some(on_execute)) };
|
||||||
unsafe { vb_set_exception_callback(self.sim, Some(on_exception)) };
|
unsafe { vb_set_exception_callback(self.sim, Some(on_exception)) };
|
||||||
|
unsafe { vb_set_fetch_callback(self.sim, Some(on_fetch)) };
|
||||||
} else {
|
} else {
|
||||||
if !state.needs_execute_callback() {
|
if !state.needs_execute_callback() {
|
||||||
unsafe { vb_set_execute_callback(self.sim, None) };
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
||||||
|
@ -816,9 +845,11 @@ impl Sim {
|
||||||
reason
|
reason
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_sim_event(&mut self) -> Option<SimEvent> {
|
pub fn take_profiler_updates(&mut self) -> (Option<SimEvent>, Option<InlineStack>) {
|
||||||
let data = self.get_state();
|
let data = self.get_state();
|
||||||
data.monitor.event.take()
|
let event = data.monitor.event.take();
|
||||||
|
let inline_stack = data.monitor.new_inline_stack.take();
|
||||||
|
(event, inline_stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_state(&mut self) -> &mut VBState {
|
fn get_state(&mut self) -> &mut VBState {
|
||||||
|
|
|
@ -6,7 +6,9 @@ use std::{
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::{select, sync::mpsc};
|
use tokio::{select, sync::mpsc};
|
||||||
|
|
||||||
use crate::emulator::{EmulatorClient, EmulatorCommand, GameInfo, ProfileEvent, SimEvent, SimId};
|
use crate::emulator::{
|
||||||
|
EmulatorClient, EmulatorCommand, GameInfo, InlineStack, ProfileEvent, SimEvent, SimId,
|
||||||
|
};
|
||||||
use recording::Recording;
|
use recording::Recording;
|
||||||
use state::ProgramState;
|
use state::ProgramState;
|
||||||
|
|
||||||
|
@ -131,11 +133,18 @@ async fn run_profile(
|
||||||
async fn handle_event(event: ProfileEvent, session: &mut ProfilerSession) -> Result<()> {
|
async fn handle_event(event: ProfileEvent, session: &mut ProfilerSession) -> Result<()> {
|
||||||
match event {
|
match event {
|
||||||
ProfileEvent::Start { info } => session.start_profiling(info).await,
|
ProfileEvent::Start { info } => session.start_profiling(info).await,
|
||||||
ProfileEvent::Update { cycles, event } => {
|
ProfileEvent::Update {
|
||||||
|
cycles,
|
||||||
|
event,
|
||||||
|
inline_stack,
|
||||||
|
} => {
|
||||||
session.track_elapsed_cycles(cycles);
|
session.track_elapsed_cycles(cycles);
|
||||||
if let Some(event) = event {
|
if let Some(event) = event {
|
||||||
session.track_event(event)?;
|
session.track_event(event)?;
|
||||||
}
|
}
|
||||||
|
if let Some(stack) = inline_stack {
|
||||||
|
session.track_inline_stack(stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -233,6 +242,12 @@ impl ProfilerSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn track_inline_stack(&mut self, inline_stack: InlineStack) {
|
||||||
|
if let Some(program) = &mut self.program {
|
||||||
|
program.track_inline_stack(inline_stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn start_recording(&mut self) {
|
fn start_recording(&mut self) {
|
||||||
if let Some(program) = &self.program {
|
if let Some(program) = &self.program {
|
||||||
self.recording = Some(Recording::new(program));
|
self.recording = Some(Recording::new(program));
|
||||||
|
|
|
@ -89,14 +89,21 @@ impl Recording {
|
||||||
thread: ThreadHandle,
|
thread: ThreadHandle,
|
||||||
frames: &[StackFrame],
|
frames: &[StackFrame],
|
||||||
) -> Option<StackHandle> {
|
) -> Option<StackHandle> {
|
||||||
self.profile.intern_stack_frames(
|
let frames = frames
|
||||||
thread,
|
.iter()
|
||||||
frames.iter().map(|f| FrameInfo {
|
.map(|f| {
|
||||||
frame: Frame::InstructionPointer(f.address as u64),
|
let frame = match f {
|
||||||
category_pair: CategoryHandle::OTHER.into(),
|
StackFrame::Address(address) => Frame::InstructionPointer(*address as u64),
|
||||||
flags: FrameFlags::empty(),
|
StackFrame::Label(label) => Frame::Label(self.profile.intern_string(label)),
|
||||||
}),
|
};
|
||||||
)
|
FrameInfo {
|
||||||
|
frame,
|
||||||
|
category_pair: CategoryHandle::OTHER.into(),
|
||||||
|
flags: FrameFlags::empty(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.profile.intern_stack_frames(thread, frames.into_iter())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use fxprof_processed_profile::LibraryInfo;
|
use fxprof_processed_profile::LibraryInfo;
|
||||||
|
|
||||||
use crate::emulator::GameInfo;
|
use crate::emulator::{GameInfo, InlineStack};
|
||||||
|
|
||||||
pub struct ProgramState {
|
pub struct ProgramState {
|
||||||
info: Arc<GameInfo>,
|
info: Arc<GameInfo>,
|
||||||
|
@ -11,20 +11,16 @@ pub struct ProgramState {
|
||||||
context_stack: Vec<u16>,
|
context_stack: Vec<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StackFrame {
|
pub enum StackFrame {
|
||||||
pub address: u32,
|
Address(u32),
|
||||||
|
Label(Arc<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const RESET_CODE: u16 = 0xfff0;
|
pub const RESET_CODE: u16 = 0xfff0;
|
||||||
impl ProgramState {
|
impl ProgramState {
|
||||||
pub async fn new(info: Arc<GameInfo>) -> Self {
|
pub async fn new(info: Arc<GameInfo>) -> Self {
|
||||||
let mut call_stacks = HashMap::new();
|
let mut call_stacks = HashMap::new();
|
||||||
call_stacks.insert(
|
call_stacks.insert(RESET_CODE, vec![StackFrame::Address(0xfffffff0)]);
|
||||||
RESET_CODE,
|
|
||||||
vec![StackFrame {
|
|
||||||
address: 0xfffffff0,
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
Self {
|
Self {
|
||||||
info,
|
info,
|
||||||
call_stacks,
|
call_stacks,
|
||||||
|
@ -53,7 +49,7 @@ impl ProgramState {
|
||||||
let Some(stack) = self.call_stacks.get_mut(code) else {
|
let Some(stack) = self.call_stacks.get_mut(code) else {
|
||||||
bail!("missing stack {code:04x}");
|
bail!("missing stack {code:04x}");
|
||||||
};
|
};
|
||||||
stack.push(StackFrame { address });
|
stack.push(StackFrame::Address(address));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +85,7 @@ impl ProgramState {
|
||||||
self.context_stack.push(code);
|
self.context_stack.push(code);
|
||||||
if self
|
if self
|
||||||
.call_stacks
|
.call_stacks
|
||||||
.insert(code, vec![StackFrame { address }])
|
.insert(code, vec![StackFrame::Address(address)])
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
bail!("{code:04x} fired twice");
|
bail!("{code:04x} fired twice");
|
||||||
|
@ -109,4 +105,20 @@ impl ProgramState {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn track_inline_stack(&mut self, inline_stack: InlineStack) {
|
||||||
|
let Some(code) = self.context_stack.last() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(call_stack) = self.call_stacks.get_mut(code) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
while call_stack
|
||||||
|
.pop_if(|f| matches!(f, StackFrame::Label(_)))
|
||||||
|
.is_some()
|
||||||
|
{}
|
||||||
|
for label in inline_stack.iter() {
|
||||||
|
call_stack.push(StackFrame::Label(label.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue