crates/mimir-room/src/symbols.rs
//! The binary stream's own facts, beyond the one `linked` flag: every linked
//! symbol with its size / section / role / artifact — and the record of the
//! node ids the fold MERGED (several rows, one id).
use anyhow::Result;
use serde::Serialize;
use crate::Room;
use crate::evidence::Answer;
use crate::query::Mask;
use crate::snapshot::{DupKey, NONE};
/// One linked symbol of an artifact.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct SymbolView<'a> {
/// The node it belongs to (an item, a mono instance, an FFI import).
pub node: u64,
/// The linker name (mangled).
pub symbol: &'a str,
pub demangled: Option<&'a str>,
/// `item` · `mono` · `closure` · `shim` · `import` (`mimir_schema::role`).
pub role: Option<&'a str>,
/// The artifact that links it (`name@target-triple:profile`).
pub artifact: &'a str,
/// The artifact's file name.
pub binary: Option<&'a str>,
pub section: Option<&'a str>,
/// Bytes; `None` for an import (resolved by the dynamic linker).
pub size: Option<u64>,
}
impl Room {
fn symbol_at(&self, i: usize) -> SymbolView<'_> {
let s = &self.snap.symbols;
let st = |v: u32| (v != NONE).then(|| s.strs[v as usize].as_str());
SymbolView {
node: s.node[i] as u64,
symbol: s.symbol.get(i),
demangled: Some(s.demangled.get(i)).filter(|d| !d.is_empty()),
role: st(s.role[i]),
artifact: st(s.artifact[i]).unwrap_or(""),
binary: st(s.binary[i]),
section: st(s.section[i]),
size: (s.size[i] != u64::MAX).then_some(s.size[i]),
}
}
/// The linked symbols of one node (several: one per artifact, plus its
/// closures and shims), by symbol name.
pub fn symbols_of(&self, ordinal: u64) -> Vec<SymbolView<'_>> {
let s = &self.snap.symbols;
let o = ordinal as u32;
let at = s.node.partition_point(|n| *n < o);
(at..s.len()).take_while(|i| s.node[*i] == o).map(|i| self.symbol_at(i)).collect()
}
/// Bytes linked for one node: the sum of its symbols' sizes over every
/// artifact (0 when nothing of it is linked).
pub fn linked_bytes(&self, ordinal: u64) -> u64 {
self.symbols_of(ordinal).iter().filter_map(|s| s.size).sum()
}
/// Every linked symbol row (of the nodes in `within`, when given), by
/// (node, symbol); `cap` = return at most that many.
pub fn symbols(&self, within: Option<&Mask>, cap: Option<usize>) -> Result<Answer<Vec<SymbolView<'_>>>> {
let mut ev = self.evidence(format!("symbols(within {:?}, cap {cap:?})", within.map(|m| m.len())), true, &[]);
if let Some(m) = within {
self.check_mask(m)?;
}
let s = &self.snap.symbols;
let keep: Vec<usize> = (0..s.len()).filter(|i| within.is_none_or(|m| m.contains(s.node[*i] as u64))).collect();
let shown = cap.map_or(keep.len(), |c| c.min(keep.len()));
ev.cap(shown, keep.len());
Ok(Answer::new(keep[..shown].iter().map(|i| self.symbol_at(*i)).collect(), ev))
}
/// The node ids the fold merged: an id that several open rows describe
/// with DIFFERENT payloads (`distinct_payloads > 1` — two owners
/// disagree about the item), or that one dump repeated inside a unit
/// (`key_dup_rows > 0`). By ordinal. The ledger (`ingest_runs.
/// key_collisions`) counts the second kind per commit; this is the list.
pub fn duplicate_keys(&self, within: Option<&Mask>) -> Result<Answer<Vec<DupKey>>> {
let ev = self.evidence(format!("duplicate_keys(within {:?})", within.map(|m| m.len())), false, &[]);
if let Some(m) = within {
self.check_mask(m)?;
}
let v: Vec<DupKey> = self.snap.dups.iter().filter(|d| within.is_none_or(|m| m.contains(d.node))).cloned().collect();
Ok(Answer::new(v, ev))
}
}