Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

crates/mimir-driver/src/walk/mir.rs

//! MIR facts: every fn / method body's `mir_promoted` MIR as name-free,
//! compact facts ([`mimir_schema::mir`] is the grammar).
//!
//! ## Which MIR, and why `mir_promoted`
//!
//! `optimized_mir` depends on `-C opt-level` / `-Z mir-opt-level` (inlining,
//! const propagation, CFG simplification all move blocks): the same source
//! would give other facts in a release build. `mir_promoted` is the built MIR
//! after `SimplifyCfg::Initial` / promotion and BEFORE every optimisation —
//! the borrow checker's input, the same at every opt level. (`mir_built` is
//! as stable but still holds the goto chains `SimplifyCfg` removes.)
//!
//! ## Getting it after analysis
//!
//! The walk runs in `after_analysis`. By then rustc may have STOLEN a body's
//! `mir_promoted` (`mir_drops_elaborated_and_const_checked` takes it: always
//! for const contexts, for every body when the build codegens). So the dump
//! callbacks override that query ([`override_queries`]): before the original
//! provider steals it, a fn-like body's `mir_promoted` is cloned into a
//! thread-local stash. Any body NOT stolen (the usual `cargo check` case, and
//! a body incremental compilation never built) is read from the query itself.
//! A body found neither way is counted (`mir_missing`), never guessed.

use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap};
use std::fmt::Write as _;
use std::sync::OnceLock;

use mimir_schema::mir::{self as m, Pos, ITEM_CALLEE_ARGS_END, ITEM_CALLEE_END};
use mimir_schema::moniker::{self, Suffix};
use mimir_schema::{kind, EdgeKind, Level};
use rustc_data_structures::steal::Steal;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_middle::mir::{
    AggregateKind, BasicBlock, Body, BorrowKind, Local, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, TerminatorKind,
};
use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeVisitableExt, TypingEnv};
use rustc_span::{FileName, Span};

use super::{plain_ty, Walker};

// ------------------------------------------------------------ the stash

type DropsElaborated = for<'tcx> fn(TyCtxt<'tcx>, LocalDefId) -> &'tcx Steal<Body<'tcx>>;
static ORIG_DROPS_ELABORATED: OnceLock<DropsElaborated> = OnceLock::new();

thread_local! {
    /// `mir_promoted` bodies cloned before rustc stole them, lifetime-erased.
    /// SAFETY: every `Body<'tcx>` in here refers only to data interned in the
    /// ONE `TyCtxt` of this compiler session; it is read back (and dropped)
    /// in that session's `after_analysis`, while the context lives, and the
    /// stash is cleared there.
    static STASH: RefCell<HashMap<LocalDefId, Body<'static>>> = RefCell::new(HashMap::new());
}

/// `Config::override_queries` of the dump callbacks.
pub fn override_queries(_sess: &rustc_session::Session, providers: &mut rustc_middle::util::Providers) {
    let _ = ORIG_DROPS_ELABORATED.set(providers.queries.mir_drops_elaborated_and_const_checked);
    providers.queries.mir_drops_elaborated_and_const_checked = drops_elaborated;
}

fn drops_elaborated<'tcx>(tcx: TyCtxt<'tcx>, def: LocalDefId) -> &'tcx Steal<Body<'tcx>> {
    if fn_like(tcx, def) {
        let (b, _) = tcx.mir_promoted(def);
        if !b.is_stolen() {
            let body: Body<'tcx> = b.borrow().clone();
            // SAFETY: see STASH.
            let body: Body<'static> = unsafe { std::mem::transmute::<Body<'tcx>, Body<'static>>(body) };
            STASH.with(|s| s.borrow_mut().insert(def, body));
        }
    }
    let orig = ORIG_DROPS_ELABORATED.get().expect("override_queries ran");
    orig(tcx, def)
}

fn fn_like(tcx: TyCtxt<'_>, def: LocalDefId) -> bool {
    matches!(tcx.def_kind(def), DefKind::Fn | DefKind::AssocFn | DefKind::Closure)
}

/// Drop whatever the stash still holds (end of the walk).
pub fn clear_stash() {
    STASH.with(|s| s.borrow_mut().clear());
}

/// Run `f` on `def`'s `mir_promoted` body — the stashed clone, or the query's
/// own when it is still there. `None`: stolen and not stashed.
fn with_body<'tcx, R>(tcx: TyCtxt<'tcx>, def: LocalDefId, f: impl FnOnce(&Body<'tcx>) -> R) -> Option<R> {
    if let Some(b) = STASH.with(|s| s.borrow_mut().remove(&def)) {
        // SAFETY: see STASH — the same session's context.
        let b: Body<'tcx> = unsafe { std::mem::transmute::<Body<'static>, Body<'tcx>>(b) };
        return Some(f(&b));
    }
    let (b, _) = tcx.mir_promoted(def);
    if b.is_stolen() {
        return None;
    }
    let g = b.borrow();
    Some(f(&g))
}

// ------------------------------------------------------------ the mode

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
    Off,
    Fingerprint,
    Full,
}

impl Mode {
    pub fn of_env(local: bool) -> Mode {
        match std::env::var(mimir_driver::ENV_MIR).as_deref() {
            Ok("off") => Mode::Off,
            Ok("fingerprint") => Mode::Fingerprint,
            Ok("all") => Mode::Full,
            _ if local => Mode::Full,
            _ => Mode::Fingerprint,
        }
    }
    pub fn as_str(self) -> &'static str {
        match self {
            Mode::Off => "off",
            Mode::Fingerprint => "fingerprint",
            Mode::Full => "full",
        }
    }
}

/// The counters of the MIR pass (→ `CrateStats.mir_*`).
#[derive(Default)]
pub struct MirStats {
    pub mode: &'static str,
    pub bodies: usize,
    pub nested: usize,
    pub blocks: usize,
    pub statements: usize,
    pub missing: usize,
    pub payload_bytes: usize,
    pub ms: u128,
}

// ------------------------------------------------------------ one body

/// One block, rendered.
struct BlockOut {
    /// `3` / `c1.3`
    name: String,
    fp: u64,
    term_kind: &'static str,
    succ: Vec<String>,
    /// the resolved callee of a call terminator (text holds `\0` for it)
    callee: Option<DefId>,
    /// full mode only
    term: String,
    stmts: Vec<String>,
    locals: Vec<(u32, String)>,
    def_use: Vec<(u32, u32)>,
}

struct Ctx<'a, 'tcx> {
    tcx: TyCtxt<'tcx>,
    body: &'a Body<'tcx>,
    typing_env: TypingEnv<'tcx>,
    full: bool,
    /// the fn's file (source map name) and first line: positions are relative
    base: Option<(FileName, usize)>,
    ty_cache: &'a mut HashMap<Ty<'tcx>, String>,
}

/// What one statement / terminator reads and writes (locals).
#[derive(Default)]
struct Rw {
    reads: Vec<Local>,
    write: Option<Local>,
}

impl<'a, 'tcx> Ctx<'a, 'tcx> {
    fn ty(&mut self, t: Ty<'tcx>) -> String {
        if let Some(s) = self.ty_cache.get(&t) {
            return s.clone();
        }
        let s = mir_ty(t);
        self.ty_cache.insert(t, s.clone());
        s
    }

    fn place(&mut self, p: Place<'tcx>, rw: &mut Rw, out: &mut String) {
        let _ = write!(out, "_{}", p.local.as_u32());
        rw.reads.push(p.local);
        for e in p.projection.iter() {
            match e {
                ProjectionElem::Deref => out.push('*'),
                ProjectionElem::Field(f, _) => {
                    let _ = write!(out, ".{}", f.as_u32());
                }
                ProjectionElem::Index(l) => {
                    let _ = write!(out, "[_{}]", l.as_u32());
                    rw.reads.push(l);
                }
                ProjectionElem::ConstantIndex { offset, from_end, .. } => {
                    let _ = write!(out, "[{}{offset}]", if from_end { "-" } else { "" });
                }
                ProjectionElem::Subslice { from, to, from_end } => {
                    let _ = write!(out, "[{from}..{}{to}]", if from_end { "-" } else { "" });
                }
                ProjectionElem::Downcast(_, v) => {
                    let _ = write!(out, "~{}", v.as_u32());
                }
                #[allow(unreachable_patterns)]
                _ => out.push('#'),
            }
        }
    }

    /// The written place of an assignment: a def of its local unless it is
    /// written THROUGH a pointer (`(*_3).0 = …` reads `_3`).
    fn dest(&mut self, p: Place<'tcx>, rw: &mut Rw, out: &mut String) {
        let mut tmp = Rw::default();
        self.place(p, &mut tmp, out);
        let through_ptr = p.projection.iter().any(|e| matches!(e, ProjectionElem::Deref));
        if through_ptr {
            rw.reads.extend(tmp.reads);
        } else {
            rw.write = Some(p.local);
            rw.reads.extend(tmp.reads.into_iter().skip(1));
        }
    }

    fn operand(&mut self, o: &Operand<'tcx>, rw: &mut Rw, out: &mut String) {
        match o {
            Operand::Copy(p) => self.place(*p, rw, out),
            Operand::Move(p) => {
                out.push('^');
                self.place(*p, rw, out);
            }
            Operand::Constant(c) => {
                let t = self.ty(c.const_.ty());
                let _ = write!(out, "k:{t}");
            }
            #[allow(unreachable_patterns)]
            _ => out.push('?'),
        }
    }

    fn operands(&mut self, os: impl IntoIterator<Item = &'a Operand<'tcx>>, rw: &mut Rw, out: &mut String)
    where
        'tcx: 'a,
    {
        for (i, o) in os.into_iter().enumerate() {
            if i > 0 {
                out.push(',');
            }
            self.operand(o, rw, out);
        }
    }

    fn rvalue(&mut self, rv: &'a Rvalue<'tcx>, rw: &mut Rw, out: &mut String) {
        match rv {
            Rvalue::Use(o, _) => {
                out.push_str("use(");
                self.operand(o, rw, out);
                out.push(')');
            }
            Rvalue::Repeat(o, _) => {
                out.push_str("repeat(");
                self.operand(o, rw, out);
                out.push(')');
            }
            Rvalue::Ref(_, bk, p) => {
                out.push_str(match bk {
                    BorrowKind::Shared => "ref(",
                    BorrowKind::Mut { .. } => "refmut(",
                    BorrowKind::Fake(_) => "reffake(",
                });
                self.place(*p, rw, out);
                out.push(')');
            }
            Rvalue::ThreadLocalRef(_) => out.push_str("tls()"),
            Rvalue::RawPtr(k, p) => {
                let _ = write!(out, "raw.{k:?}(");
                self.place(*p, rw, out);
                out.push(')');
            }
            Rvalue::Cast(k, o, t) => {
                let _ = write!(out, "cast.{}(", cast_word(&format!("{k:?}")));
                self.operand(o, rw, out);
                let t = self.ty(*t);
                let _ = write!(out, "):{t}");
            }
            Rvalue::BinaryOp(op, ab) => {
                let _ = write!(out, "{op:?}(");
                self.operand(&ab.0, rw, out);
                out.push(',');
                self.operand(&ab.1, rw, out);
                out.push(')');
            }
            Rvalue::UnaryOp(op, o) => {
                let _ = write!(out, "{op:?}(");
                self.operand(o, rw, out);
                out.push(')');
            }
            Rvalue::Discriminant(p) => {
                out.push_str("discr(");
                self.place(*p, rw, out);
                out.push(')');
            }
            Rvalue::Aggregate(k, ops) => {
                match &**k {
                    AggregateKind::Array(_) => out.push_str("array("),
                    AggregateKind::Tuple => out.push_str("tuple("),
                    AggregateKind::Adt(_, v, _, _, f) => {
                        let _ = write!(out, "adt~{}{}(", v.as_u32(), if f.is_some() { "u" } else { "" });
                    }
                    AggregateKind::Closure(..) => out.push_str("closure("),
                    AggregateKind::Coroutine(..) => out.push_str("coroutine("),
                    AggregateKind::CoroutineClosure(..) => out.push_str("coroutine_closure("),
                    AggregateKind::RawPtr(..) => out.push_str("rawptr("),
                }
                self.operands(ops.iter(), rw, out);
                out.push(')');
            }
            Rvalue::CopyForDeref(p) => {
                out.push_str("deref_copy(");
                self.place(*p, rw, out);
                out.push(')');
            }
            Rvalue::WrapUnsafeBinder(o, _) => {
                out.push_str("wrap(");
                self.operand(o, rw, out);
                out.push(')');
            }
            #[allow(unreachable_patterns)]
            _ => out.push_str("rvalue()"),
        }
    }

    fn pos(&self, span: Span) -> Option<Pos> {
        let (file, base) = self.base.as_ref()?;
        if span.is_dummy() {
            return None;
        }
        let macro_site = span.from_expansion();
        let span = span.source_callsite();
        let lo = self.tcx.sess.source_map().lookup_char_pos(span.lo());
        if &lo.file.name != file {
            return None;
        }
        Some(Pos { dline: lo.line as i32 - *base as i32, col: lo.col.0 as u32 + 1, macro_site })
    }

    /// The resolved callee of a call's `func` (a trait method with concrete
    /// args → the impl's method rustc picks).
    fn callee_of(&mut self, func: &Operand<'tcx>) -> Option<DefId> {
        let (did, args) = func.const_fn_def()?;
        let tcx = self.tcx;
        let mut target = did;
        if tcx.trait_of_assoc(did).is_some() && !(args.has_non_region_param() || args.has_infer() || args.references_error() || args.has_escaping_bound_vars()) {
            let args = tcx.erase_and_anonymize_regions(args);
            if let Ok(Some(inst)) = Instance::try_resolve(tcx, self.typing_env, did, args) {
                if let ty::InstanceKind::Item(d) = inst.def {
                    target = d;
                }
            }
        }
        Some(target)
    }

    fn block(&mut self, prefix: &str, bb: BasicBlock) -> BlockOut {
        let data = &self.body.basic_blocks[bb];
        let mut canon_order: Vec<u32> = Vec::new();
        let mut canon = String::new();
        let mut stmts = Vec::new();
        let mut touched: BTreeSet<u32> = BTreeSet::new();
        let mut du: BTreeSet<(u32, u32)> = BTreeSet::new();
        let mut n_stmts = 0usize;
        for st in &data.statements {
            let mut rw = Rw::default();
            let mut text = String::new();
            match &st.kind {
                StatementKind::Assign(b) => {
                    let (p, rv) = &**b;
                    self.dest(*p, &mut rw, &mut text);
                    text.push('=');
                    self.rvalue(rv, &mut rw, &mut text);
                }
                StatementKind::SetDiscriminant { place, variant_index } => {
                    self.dest(**place, &mut rw, &mut text);
                    let _ = write!(text, "=setdiscr~{}", variant_index.as_u32());
                }
                StatementKind::Intrinsic(i) => match &**i {
                    NonDivergingIntrinsic::Assume(o) => {
                        text.push_str("assume(");
                        self.operand(o, &mut rw, &mut text);
                        text.push(')');
                    }
                    NonDivergingIntrinsic::CopyNonOverlapping(c) => {
                        text.push_str("copy_nonoverlapping(");
                        self.operand(&c.src, &mut rw, &mut text);
                        text.push(',');
                        self.operand(&c.dst, &mut rw, &mut text);
                        text.push(',');
                        self.operand(&c.count, &mut rw, &mut text);
                        text.push(')');
                    }
                },
                // storage markers, fake reads, retags, user type ascriptions,
                // place mentions, coverage, nops: no data, no flow
                _ => continue,
            }
            n_stmts += 1;
            self.flow(&rw, &mut touched, &mut du);
            canon.push_str(&m::canonical_locals(&text, &mut canon_order));
            canon.push('\n');
            if self.full && stmts.len() < m::STMTS_PER_BLOCK {
                if let Some(p) = self.pos(st.source_info.span) {
                    p.push_to(&mut text);
                }
                stmts.push(text);
            }
        }
        // the terminator
        let mut rw = Rw::default();
        let mut text = String::new();
        let mut succ: Vec<BasicBlock> = Vec::new();
        let mut callee: Option<DefId> = None;
        let term_kind: &'static str = match data.terminator.as_ref().map(|t| &t.kind) {
            None => "unreachable",
            Some(k) => match k {
                TerminatorKind::Goto { target } => {
                    succ.push(*target);
                    text.push_str("goto");
                    "goto"
                }
                TerminatorKind::SwitchInt { discr, targets } => {
                    text.push_str("switch(");
                    self.operand(discr, &mut rw, &mut text);
                    text.push(';');
                    for (i, (v, _)) in targets.iter().enumerate() {
                        if i > 0 {
                            text.push(',');
                        }
                        let _ = write!(text, "{v}");
                    }
                    text.push(')');
                    succ.extend(targets.all_targets().iter().copied());
                    "switch"
                }
                TerminatorKind::UnwindResume => {
                    text.push_str("resume");
                    "resume"
                }
                TerminatorKind::UnwindTerminate(_) => {
                    text.push_str("terminate");
                    "terminate"
                }
                TerminatorKind::Return => {
                    text.push_str("return");
                    "return"
                }
                TerminatorKind::Unreachable => {
                    text.push_str("unreachable");
                    "unreachable"
                }
                TerminatorKind::Drop { place, target, .. } => {
                    text.push_str("drop(");
                    self.place(*place, &mut rw, &mut text);
                    text.push(')');
                    succ.push(*target);
                    "drop"
                }
                TerminatorKind::Call { func, args, destination, target, .. } => {
                    text.push_str("call ");
                    callee = self.callee_of(func);
                    match callee {
                        Some(_) => text.push('\u{0}'),
                        None => self.operand(func, &mut rw, &mut text),
                    }
                    text.push(ITEM_CALLEE_END);
                    self.operands(args.iter().map(|a| &a.node), &mut rw, &mut text);
                    text.push(ITEM_CALLEE_ARGS_END);
                    text.push_str("->");
                    self.dest(*destination, &mut rw, &mut text);
                    succ.extend(target.iter().copied());
                    "call"
                }
                TerminatorKind::TailCall { func, args, .. } => {
                    text.push_str("tailcall ");
                    callee = self.callee_of(func);
                    match callee {
                        Some(_) => text.push('\u{0}'),
                        None => self.operand(func, &mut rw, &mut text),
                    }
                    text.push(ITEM_CALLEE_END);
                    self.operands(args.iter().map(|a| &a.node), &mut rw, &mut text);
                    text.push(ITEM_CALLEE_ARGS_END);
                    "tailcall"
                }
                TerminatorKind::Assert { cond, expected, msg, target, .. } => {
                    let _ = write!(text, "assert.{}({expected},", assert_word(msg));
                    self.operand(cond, &mut rw, &mut text);
                    text.push(')');
                    succ.push(*target);
                    "assert"
                }
                TerminatorKind::Yield { value, resume, resume_arg, .. } => {
                    text.push_str("yield(");
                    self.operand(value, &mut rw, &mut text);
                    text.push_str(")->");
                    self.dest(*resume_arg, &mut rw, &mut text);
                    succ.push(*resume);
                    "yield"
                }
                TerminatorKind::CoroutineDrop => {
                    text.push_str("coroutine_drop");
                    "coroutine_drop"
                }
                TerminatorKind::FalseEdge { real_target, .. } => {
                    text.push_str("false_edge");
                    succ.push(*real_target);
                    "false_edge"
                }
                TerminatorKind::FalseUnwind { real_target, .. } => {
                    text.push_str("false_unwind");
                    succ.push(*real_target);
                    "false_unwind"
                }
                TerminatorKind::InlineAsm { targets, .. } => {
                    text.push_str("asm");
                    succ.extend(targets.iter().copied());
                    "asm"
                }
            },
        };
        self.flow(&rw, &mut touched, &mut du);
        canon.push_str(&m::canonical_locals(&text, &mut canon_order));
        canon.push('\n');
        // the block's locals' types, in canonical order: what the block does
        // TO WHAT — part of the fingerprint
        for l in &canon_order {
            let t = self.ty(self.body.local_decls[Local::from_u32(*l)].ty);
            canon.push_str(&t);
            canon.push(';');
        }
        let fp = m::fingerprint(&canon);
        // only normal flow: a cleanup block is no successor
        succ.retain(|s| !self.body.basic_blocks[*s].is_cleanup);
        succ.dedup();
        let succ = succ.into_iter().map(|s| format!("{prefix}{}", s.as_u32())).collect();
        let (term, locals) = if self.full {
            if let Some(p) = data.terminator.as_ref().and_then(|t| self.pos(t.source_info.span)) {
                p.push_to(&mut text);
            }
            let locals = touched.iter().map(|l| (*l, self.ty(self.body.local_decls[Local::from_u32(*l)].ty))).collect();
            (text, locals)
        } else {
            (String::new(), Vec::new())
        };
        let _ = n_stmts;
        BlockOut { name: format!("{prefix}{}", bb.as_u32()), fp, term_kind, succ, callee, term, stmts, locals, def_use: if self.full { du.into_iter().collect() } else { Vec::new() } }
    }

    fn flow(&self, rw: &Rw, touched: &mut BTreeSet<u32>, du: &mut BTreeSet<(u32, u32)>) {
        for r in &rw.reads {
            touched.insert(r.as_u32());
        }
        if let Some(w) = rw.write {
            touched.insert(w.as_u32());
            for r in &rw.reads {
                if *r != w {
                    du.insert((r.as_u32(), w.as_u32()));
                }
            }
        }
    }
}

/// `PointerCoercion(Unsize, Implicit)` → `PointerCoercion.Unsize`.
fn cast_word(dbg: &str) -> String {
    let mut s: String = dbg.chars().filter(|c| c.is_ascii_alphanumeric() || *c == '(' || *c == ',').collect();
    if let Some(i) = s.find(',') {
        s.truncate(i);
    }
    s.replace('(', ".")
}

fn assert_word<O>(msg: &rustc_middle::mir::AssertKind<O>) -> String {
    use rustc_middle::mir::AssertKind as A;
    match msg {
        A::BoundsCheck { .. } => "bounds".into(),
        A::Overflow(op, ..) => format!("overflow.{op:?}"),
        A::OverflowNeg(_) => "overflow_neg".into(),
        A::DivisionByZero(_) => "div_zero".into(),
        A::RemainderByZero(_) => "rem_zero".into(),
        A::ResumedAfterReturn(_) => "resumed_after_return".into(),
        A::ResumedAfterPanic(_) => "resumed_after_panic".into(),
        A::MisalignedPointerDereference { .. } => "misaligned".into(),
        A::NullPointerDereference => "null_deref".into(),
        #[allow(unreachable_patterns)]
        _ => "other".into(),
    }
}

/// A type as MIR facts print it: `plain_ty`, closures / coroutines without
/// their position (a closure's type names its file:line:col — code motion
/// must not move a key).
fn mir_ty(t: Ty<'_>) -> String {
    let s = plain_ty(t);
    if !s.contains('@') {
        return s;
    }
    // `{closure@src/a.rs:3:9}` → `{closure}`
    let mut out = String::with_capacity(s.len());
    let mut rest = s.as_str();
    while let Some(i) = rest.find('@') {
        out.push_str(&rest[..i]);
        match rest[i..].find('}') {
            Some(j) => rest = &rest[i + j..],
            None => {
                rest = "";
            }
        }
    }
    out.push_str(rest);
    out
}

// ------------------------------------------------------------ the pass

impl<'tcx> Walker<'tcx> {
    /// The MIR pass: per fn / method (closures folded in) a `mir_body` node,
    /// and in full mode a `mir_block` node per block + `CFG_SUCC` edges.
    pub(super) fn walk_mir(&mut self, mode: Mode) -> MirStats {
        let t0 = std::time::Instant::now();
        let mut st = MirStats { mode: mode.as_str(), ..Default::default() };
        if mode == Mode::Off {
            clear_stash();
            return st;
        }
        let tcx = self.tcx;
        // fn-like body owners grouped under their typeck root
        let mut nested: HashMap<LocalDefId, Vec<LocalDefId>> = HashMap::new();
        let mut roots: Vec<LocalDefId> = Vec::new();
        for owner in tcx.hir_body_owners() {
            if !fn_like(tcx, owner) {
                continue;
            }
            let root = tcx.typeck_root_def_id_local(owner);
            if root == owner {
                roots.push(owner);
            } else {
                nested.entry(root).or_default().push(owner);
            }
        }
        let mut ty_cache: HashMap<Ty<'tcx>, String> = HashMap::new();
        for root in roots {
            if !matches!(tcx.def_kind(root), DefKind::Fn | DefKind::AssocFn) || self.is_test_marker(root) || self.is_harness_item(root) {
                continue;
            }
            let d = root.to_def_id();
            if self.node_kind(d).is_none() {
                continue;
            }
            let fn_id = self.moniker(d);
            let base = {
                let span = tcx.source_span(root).source_callsite();
                let lo = tcx.sess.source_map().lookup_char_pos(span.lo());
                Some((lo.file.name.clone(), lo.line))
            };
            let typing_env = TypingEnv::post_analysis(tcx, d);
            let mut bodies = vec![(String::new(), root)];
            for (k, n) in nested.get(&root).into_iter().flatten().enumerate() {
                bodies.push((format!("c{}.", k + 1), *n));
            }
            let mut blocks: Vec<BlockOut> = Vec::new();
            let mut any = false;
            for (prefix, def) in &bodies {
                let full = mode == Mode::Full;                let got = with_body(tcx, *def, |body| {
                    let mut cx = Ctx { tcx, body, typing_env, full, base: base.clone(), ty_cache: &mut ty_cache };
                    body.basic_blocks.indices().filter(|bb| !body.basic_blocks[*bb].is_cleanup).take(m::BLOCKS_PER_BODY).map(|bb| cx.block(prefix, bb)).collect::<Vec<_>>()
                });
                let Some(mut got) = got else {
                    st.missing += 1;
                    continue;
                };
                any = true;
                if !prefix.is_empty() {
                    st.nested += 1;
                }
                // the callee, by its stable moniker: in the text and in the
                // block's identity (a dependency bump moves neither)
                for b in &mut got {
                    let Some(c) = b.callee else { continue };
                    let name = self.node_or_ancestor(c).map(|a| moniker::stable(&self.moniker(a)).into_owned()).unwrap_or_else(|| "?".into());
                    b.fp = m::fnv64(format!("{:016x}{name}", b.fp).as_bytes());
                    if !b.term.is_empty() {
                        b.term = b.term.replacen('\u{0}', &name, 1);
                    }
                }
                blocks.extend(got);
            }
            if !any {
                continue;
            }
            st.bodies += 1;
            st.blocks += blocks.len();
            self.emit_mir(&fn_id, &blocks, mode, &mut st);
        }
        clear_stash();
        st.ms = t0.elapsed().as_millis();
        st
    }

    fn emit_mir(&mut self, fn_id: &str, blocks: &[BlockOut], mode: Mode, st: &mut MirStats) {
        let entries: Vec<(String, u64, &str, Vec<String>)> = blocks.iter().map(|b| (b.name.clone(), b.fp, b.term_kind, b.succ.clone())).collect();
        let body_ty = m::encode_body(&entries);
        st.payload_bytes += body_ty.len();
        let mut desc = String::new();
        moniker::push_descriptor(&mut desc, Suffix::Meta, "mir");
        let row = self.part_row(format!("{fn_id}{desc}"), kind::MIR_BODY, "mir".into(), fn_id, body_ty);
        let bid = row.id.clone();
        self.push_node(row);
        self.push_edge(fn_id.to_string(), bid, EdgeKind::Contains, None, None);
        if mode != Mode::Full {
            return;
        }
        let id_of = |name: &str| {
            let mut d = String::new();
            moniker::push_descriptor(&mut d, Suffix::Meta, &format!("bb{name}"));
            format!("{fn_id}{d}")
        };
        for b in blocks {
            st.statements += b.stmts.len();
            let ty = m::encode_block(b.fp, &b.term, &b.stmts, &b.locals, &b.def_use);
            st.payload_bytes += ty.len();
            let id = id_of(&b.name);
            let mut row = self.part_row(id.clone(), kind::MIR_BLOCK, format!("bb{}", b.name), fn_id, ty);
            row.level = Level::Part;
            self.push_node(row);
            for s in &b.succ {
                self.push_edge(id.clone(), id_of(s), EdgeKind::CfgSucc, None, None);
            }
        }
    }
}