crates/mimir-room/src/mir.rs
//! MIR facts: a fn's `mir_body` node lives IN the room; its `mir_block`
//! payloads and the CFG_SUCC edges between them do NOT ([`mimir_schema::mir`]
//! is the payload grammar).
//!
//! The body is a PART under its fn, found BY MONIKER (`<fn>mir:`): every
//! block's fingerprint, terminator kind and successors — the control flow
//! is read from it, never from edges. The blocks (`<fn>bb3:`: statements,
//! locals, def-use) were most of the room's bytes (the nordisk room: 14.6 M
//! block events, 2 GB of payload) for ONE lens, so the fold drops them and
//! that lens reads them per repo from the lake, bounded ([`MirSide::load`]).
//! A fn the dump described without MIR (an older driver, `MIMIR_MIR=off`)
//! has no body node: [`Room::mir_body`] is `None` and every consumer falls
//! back to what it read before.
use std::collections::HashMap;
use mimir_ingest::EventLog;
use mimir_schema::kind;
use mimir_schema::mir::{self, Block, BodyBlock, Pos};
use crate::Room;
/// The MIR shape of one fn, as its `mir_body` node holds it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MirBody<'a> {
/// The `mir_body` node.
pub ordinal: u64,
pub blocks: Vec<BodyBlock<'a>>,
}
/// One `mir_block` payload, parsed, with its positions made absolute.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MirBlockView<'a> {
/// `3` / `c1.3` (the body's block name).
pub block: &'a str,
pub facts: Block<'a>,
/// Source lines of its statements and terminator (absolute, sorted,
/// distinct): the fn's first line + each position's offset.
pub lines: Vec<u32>,
}
/// The `mir_block` payloads of ONE repo, read from the lake beside the room
/// (STABLE block moniker → payload), at most `cap` payload bytes.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MirSide {
blocks: HashMap<String, String>,
/// Payload bytes held.
pub bytes: usize,
/// The cap stopped the read: blocks past it are absent (a fn without
/// its blocks falls back to its fact lines — a lower bound, said so).
pub capped: bool,
}
impl MirSide {
/// The repo's open `mir_block` payloads at `upto` (`None` = the head).
pub async fn load(log: &EventLog, repo: &str, upto: Option<i64>, cap: usize) -> anyhow::Result<Self> {
let p = log.mir_block_payloads(repo, upto, cap).await?;
Ok(Self { blocks: p.rows, bytes: p.bytes, capped: p.capped })
}
/// A side from (block moniker, payload) pairs (fixtures).
pub fn from_pairs(pairs: impl IntoIterator<Item = (String, String)>) -> Self {
let blocks: HashMap<String, String> = pairs.into_iter().map(|(k, v)| (mimir_schema::moniker::stable(&k).into_owned(), v)).collect();
let bytes = blocks.values().map(String::len).sum();
Self { blocks, bytes, capped: false }
}
pub fn len(&self) -> usize {
self.blocks.len()
}
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
/// The payload of one block moniker.
pub fn payload(&self, block_moniker: &str) -> Option<&str> {
self.blocks.get(mimir_schema::moniker::stable(block_moniker).as_ref()).map(String::as_str)
}
}
impl Room {
/// The moniker of `fn_ordinal`'s MIR body node.
pub fn mir_body_id(&self, fn_ordinal: u64) -> String {
format!("{}mir:", self.moniker(fn_ordinal))
}
/// The moniker of block `block` (`3`, `c1.3`) of a fn.
pub fn mir_block_id(&self, fn_ordinal: u64, block: &str) -> String {
let mut d = String::new();
mimir_schema::moniker::push_descriptor(&mut d, mimir_schema::moniker::Suffix::Meta, &format!("bb{block}"));
format!("{}{d}", self.moniker(fn_ordinal))
}
/// The fn's MIR body: every block's fingerprint, terminator kind and
/// successors. `None` = the room holds no MIR facts for it.
pub fn mir_body(&self, fn_ordinal: u64) -> Option<MirBody<'_>> {
let o = self.ordinal(&self.mir_body_id(fn_ordinal))?;
let n = self.node(o);
if n.kind != Some(kind::MIR_BODY) {
return None;
}
let blocks = mir::parse_body(n.ty?)?;
Some(MirBody { ordinal: o, blocks })
}
/// Does the room hold MIR facts for this fn (a body node)?
pub fn has_mir(&self, fn_ordinal: u64) -> bool {
self.mir_body(fn_ordinal).is_some()
}
/// Does `side` hold the fn's blocks (a full dump, not fingerprints only)?
pub fn has_mir_blocks(&self, fn_ordinal: u64, side: &MirSide) -> bool {
self.mir_body(fn_ordinal).and_then(|b| b.blocks.first().map(|f| side.payload(&self.mir_block_id(fn_ordinal, f.block)).is_some())).unwrap_or(false)
}
/// The fn's blocks in body order, parsed from `side`, positions absolute.
/// Empty when the side holds none of them (a dependency: fingerprints
/// only) or the room no MIR.
pub fn mir_blocks<'a>(&'a self, fn_ordinal: u64, side: &'a MirSide) -> Vec<MirBlockView<'a>> {
let Some(body) = self.mir_body(fn_ordinal) else { return Vec::new() };
let base = self.span(fn_ordinal).map(|s| s.start_line as i64);
body.blocks
.iter()
.filter_map(|b| {
let facts = mir::parse_block(side.payload(&self.mir_block_id(fn_ordinal, b.block))?)?;
let mut lines: Vec<u32> = match base {
Some(base) => facts.positions().map(|p: Pos| (base + p.dline as i64).max(1) as u32).collect(),
None => Vec::new(),
};
lines.sort_unstable();
lines.dedup();
Some(MirBlockView { block: b.block, facts, lines })
})
.collect()
}
/// The fn's control flow, `(from, to)` by block name, from the body's
/// successors (the room keeps no CFG_SUCC edge).
pub fn mir_cfg(&self, fn_ordinal: u64) -> Vec<(&str, &str)> {
let Some(body) = self.mir_body(fn_ordinal) else { return Vec::new() };
let known: std::collections::HashSet<&str> = body.blocks.iter().map(|b| b.block).collect();
body.blocks.iter().flat_map(|b| b.succ.iter().filter(|s| known.contains(*s)).map(move |s| (b.block, *s))).collect()
}
/// The fn's local-to-local def-use edges: `(a, b)` = local `a` (its MIR
/// index) is read where local `b` is written, over every block (sorted,
/// distinct). Name-free: indices, never binding names.
pub fn mir_def_use(&self, fn_ordinal: u64, side: &MirSide) -> Vec<(u32, u32)> {
let mut out: Vec<(u32, u32)> = self.mir_blocks(fn_ordinal, side).iter().flat_map(|b| b.facts.def_use.iter().copied()).collect();
out.sort_unstable();
out.dedup();
out
}
}