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);
}
}
}
}