1 //! A pretty-printer for MIR. 2 3 use std::{ 4 fmt::{Debug, Display, Write}, 5 mem, 6 }; 7 8 use hir_def::{body::Body, hir::BindingId}; 9 use hir_expand::name::Name; 10 use la_arena::ArenaMap; 11 12 use crate::{ 13 db::HirDatabase, 14 display::{ClosureStyle, HirDisplay}, 15 mir::{PlaceElem, ProjectionElem, StatementKind, TerminatorKind}, 16 ClosureId, 17 }; 18 19 use super::{ 20 AggregateKind, BasicBlockId, BorrowKind, LocalId, MirBody, Operand, Place, Rvalue, UnOp, 21 }; 22 23 macro_rules! w { 24 ($dst:expr, $($arg:tt)*) => { 25 { let _ = write!($dst, $($arg)*); } 26 }; 27 } 28 29 macro_rules! wln { 30 ($dst:expr) => { 31 { let _ = writeln!($dst); } 32 }; 33 ($dst:expr, $($arg:tt)*) => { 34 { let _ = writeln!($dst, $($arg)*); } 35 }; 36 } 37 38 impl MirBody { pretty_print(&self, db: &dyn HirDatabase) -> String39 pub fn pretty_print(&self, db: &dyn HirDatabase) -> String { 40 let hir_body = db.body(self.owner); 41 let mut ctx = MirPrettyCtx::new(self, &hir_body, db); 42 ctx.for_body(|this| match ctx.body.owner { 43 hir_def::DefWithBodyId::FunctionId(id) => { 44 let data = db.function_data(id); 45 w!(this, "fn {}() ", data.name.display(db.upcast())); 46 } 47 hir_def::DefWithBodyId::StaticId(id) => { 48 let data = db.static_data(id); 49 w!(this, "static {}: _ = ", data.name.display(db.upcast())); 50 } 51 hir_def::DefWithBodyId::ConstId(id) => { 52 let data = db.const_data(id); 53 w!( 54 this, 55 "const {}: _ = ", 56 data.name.as_ref().unwrap_or(&Name::missing()).display(db.upcast()) 57 ); 58 } 59 hir_def::DefWithBodyId::VariantId(id) => { 60 let data = db.enum_data(id.parent); 61 w!(this, "enum {} = ", data.name.display(db.upcast())); 62 } 63 hir_def::DefWithBodyId::InTypeConstId(id) => { 64 w!(this, "in type const {id:?} = "); 65 } 66 }); 67 ctx.result 68 } 69 70 // String with lines is rendered poorly in `dbg` macros, which I use very much, so this 71 // function exists to solve that. dbg(&self, db: &dyn HirDatabase) -> impl Debug72 pub fn dbg(&self, db: &dyn HirDatabase) -> impl Debug { 73 struct StringDbg(String); 74 impl Debug for StringDbg { 75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 76 f.write_str(&self.0) 77 } 78 } 79 StringDbg(self.pretty_print(db)) 80 } 81 } 82 83 struct MirPrettyCtx<'a> { 84 body: &'a MirBody, 85 hir_body: &'a Body, 86 db: &'a dyn HirDatabase, 87 result: String, 88 indent: String, 89 local_to_binding: ArenaMap<LocalId, BindingId>, 90 } 91 92 impl Write for MirPrettyCtx<'_> { write_str(&mut self, s: &str) -> std::fmt::Result93 fn write_str(&mut self, s: &str) -> std::fmt::Result { 94 let mut it = s.split('\n'); // note: `.lines()` is wrong here 95 self.write(it.next().unwrap_or_default()); 96 for line in it { 97 self.write_line(); 98 self.write(line); 99 } 100 Ok(()) 101 } 102 } 103 104 enum LocalName { 105 Unknown(LocalId), 106 Binding(Name, LocalId), 107 } 108 109 impl HirDisplay for LocalName { hir_fmt( &self, f: &mut crate::display::HirFormatter<'_>, ) -> Result<(), crate::display::HirDisplayError>110 fn hir_fmt( 111 &self, 112 f: &mut crate::display::HirFormatter<'_>, 113 ) -> Result<(), crate::display::HirDisplayError> { 114 match self { 115 LocalName::Unknown(l) => write!(f, "_{}", u32::from(l.into_raw())), 116 LocalName::Binding(n, l) => { 117 write!(f, "{}_{}", n.display(f.db.upcast()), u32::from(l.into_raw())) 118 } 119 } 120 } 121 } 122 123 impl<'a> MirPrettyCtx<'a> { for_body(&mut self, name: impl FnOnce(&mut MirPrettyCtx<'_>))124 fn for_body(&mut self, name: impl FnOnce(&mut MirPrettyCtx<'_>)) { 125 name(self); 126 self.with_block(|this| { 127 this.locals(); 128 wln!(this); 129 this.blocks(); 130 }); 131 for &closure in &self.body.closures { 132 self.for_closure(closure); 133 } 134 } 135 for_closure(&mut self, closure: ClosureId)136 fn for_closure(&mut self, closure: ClosureId) { 137 let body = match self.db.mir_body_for_closure(closure) { 138 Ok(x) => x, 139 Err(e) => { 140 wln!(self, "// error in {closure:?}: {e:?}"); 141 return; 142 } 143 }; 144 let result = mem::take(&mut self.result); 145 let indent = mem::take(&mut self.indent); 146 let mut ctx = MirPrettyCtx { 147 body: &body, 148 local_to_binding: body.binding_locals.iter().map(|(x, y)| (*y, x)).collect(), 149 result, 150 indent, 151 ..*self 152 }; 153 ctx.for_body(|this| wln!(this, "// Closure: {:?}", closure)); 154 self.result = ctx.result; 155 self.indent = ctx.indent; 156 } 157 with_block(&mut self, f: impl FnOnce(&mut MirPrettyCtx<'_>))158 fn with_block(&mut self, f: impl FnOnce(&mut MirPrettyCtx<'_>)) { 159 self.indent += " "; 160 wln!(self, "{{"); 161 f(self); 162 for _ in 0..4 { 163 self.result.pop(); 164 self.indent.pop(); 165 } 166 wln!(self, "}}"); 167 } 168 new(body: &'a MirBody, hir_body: &'a Body, db: &'a dyn HirDatabase) -> Self169 fn new(body: &'a MirBody, hir_body: &'a Body, db: &'a dyn HirDatabase) -> Self { 170 let local_to_binding = body.binding_locals.iter().map(|(x, y)| (*y, x)).collect(); 171 MirPrettyCtx { 172 body, 173 db, 174 result: String::new(), 175 indent: String::new(), 176 local_to_binding, 177 hir_body, 178 } 179 } 180 write_line(&mut self)181 fn write_line(&mut self) { 182 self.result.push('\n'); 183 self.result += &self.indent; 184 } 185 write(&mut self, line: &str)186 fn write(&mut self, line: &str) { 187 self.result += line; 188 } 189 locals(&mut self)190 fn locals(&mut self) { 191 for (id, local) in self.body.locals.iter() { 192 wln!( 193 self, 194 "let {}: {};", 195 self.local_name(id).display(self.db), 196 self.hir_display(&local.ty) 197 ); 198 } 199 } 200 local_name(&self, local: LocalId) -> LocalName201 fn local_name(&self, local: LocalId) -> LocalName { 202 match self.local_to_binding.get(local) { 203 Some(b) => LocalName::Binding(self.hir_body.bindings[*b].name.clone(), local), 204 None => LocalName::Unknown(local), 205 } 206 } 207 basic_block_id(&self, basic_block_id: BasicBlockId) -> String208 fn basic_block_id(&self, basic_block_id: BasicBlockId) -> String { 209 format!("'bb{}", u32::from(basic_block_id.into_raw())) 210 } 211 blocks(&mut self)212 fn blocks(&mut self) { 213 for (id, block) in self.body.basic_blocks.iter() { 214 wln!(self); 215 w!(self, "{}: ", self.basic_block_id(id)); 216 self.with_block(|this| { 217 for statement in &block.statements { 218 match &statement.kind { 219 StatementKind::Assign(l, r) => { 220 this.place(l); 221 w!(this, " = "); 222 this.rvalue(r); 223 wln!(this, ";"); 224 } 225 StatementKind::StorageDead(p) => { 226 wln!(this, "StorageDead({})", this.local_name(*p).display(self.db)); 227 } 228 StatementKind::StorageLive(p) => { 229 wln!(this, "StorageLive({})", this.local_name(*p).display(self.db)); 230 } 231 StatementKind::Deinit(p) => { 232 w!(this, "Deinit("); 233 this.place(p); 234 wln!(this, ");"); 235 } 236 StatementKind::Nop => wln!(this, "Nop;"), 237 } 238 } 239 match &block.terminator { 240 Some(terminator) => match &terminator.kind { 241 TerminatorKind::Goto { target } => { 242 wln!(this, "goto 'bb{};", u32::from(target.into_raw())) 243 } 244 TerminatorKind::SwitchInt { discr, targets } => { 245 w!(this, "switch "); 246 this.operand(discr); 247 w!(this, " "); 248 this.with_block(|this| { 249 for (c, b) in targets.iter() { 250 wln!(this, "{c} => {},", this.basic_block_id(b)); 251 } 252 wln!(this, "_ => {},", this.basic_block_id(targets.otherwise())); 253 }); 254 } 255 TerminatorKind::Call { func, args, destination, target, .. } => { 256 w!(this, "Call "); 257 this.with_block(|this| { 258 w!(this, "func: "); 259 this.operand(func); 260 wln!(this, ","); 261 w!(this, "args: ["); 262 this.operand_list(args); 263 wln!(this, "],"); 264 w!(this, "destination: "); 265 this.place(destination); 266 wln!(this, ","); 267 w!(this, "target: "); 268 match target { 269 Some(t) => w!(this, "{}", this.basic_block_id(*t)), 270 None => w!(this, "<unreachable>"), 271 } 272 wln!(this, ","); 273 }); 274 } 275 _ => wln!(this, "{:?};", terminator), 276 }, 277 None => wln!(this, "<no-terminator>;"), 278 } 279 }) 280 } 281 } 282 place(&mut self, p: &Place)283 fn place(&mut self, p: &Place) { 284 fn f(this: &mut MirPrettyCtx<'_>, local: LocalId, projections: &[PlaceElem]) { 285 let Some((last, head)) = projections.split_last() else { 286 // no projection 287 w!(this, "{}", this.local_name(local).display(this.db)); 288 return; 289 }; 290 match last { 291 ProjectionElem::Deref => { 292 w!(this, "(*"); 293 f(this, local, head); 294 w!(this, ")"); 295 } 296 ProjectionElem::Field(field) => { 297 let variant_data = field.parent.variant_data(this.db.upcast()); 298 let name = &variant_data.fields()[field.local_id].name; 299 match field.parent { 300 hir_def::VariantId::EnumVariantId(e) => { 301 w!(this, "("); 302 f(this, local, head); 303 let variant_name = 304 &this.db.enum_data(e.parent).variants[e.local_id].name; 305 w!( 306 this, 307 " as {}).{}", 308 variant_name.display(this.db.upcast()), 309 name.display(this.db.upcast()) 310 ); 311 } 312 hir_def::VariantId::StructId(_) | hir_def::VariantId::UnionId(_) => { 313 f(this, local, head); 314 w!(this, ".{}", name.display(this.db.upcast())); 315 } 316 } 317 } 318 ProjectionElem::TupleOrClosureField(x) => { 319 f(this, local, head); 320 w!(this, ".{}", x); 321 } 322 ProjectionElem::Index(l) => { 323 f(this, local, head); 324 w!(this, "[{}]", this.local_name(*l).display(this.db)); 325 } 326 x => { 327 f(this, local, head); 328 w!(this, ".{:?}", x); 329 } 330 } 331 } 332 f(self, p.local, &p.projection); 333 } 334 operand(&mut self, r: &Operand)335 fn operand(&mut self, r: &Operand) { 336 match r { 337 Operand::Copy(p) | Operand::Move(p) => { 338 // MIR at the time of writing doesn't have difference between move and copy, so we show them 339 // equally. Feel free to change it. 340 self.place(p); 341 } 342 Operand::Constant(c) => w!(self, "Const({})", self.hir_display(c)), 343 Operand::Static(s) => w!(self, "Static({:?})", s), 344 } 345 } 346 rvalue(&mut self, r: &Rvalue)347 fn rvalue(&mut self, r: &Rvalue) { 348 match r { 349 Rvalue::Use(op) => self.operand(op), 350 Rvalue::Ref(r, p) => { 351 match r { 352 BorrowKind::Shared => w!(self, "&"), 353 BorrowKind::Shallow => w!(self, "&shallow "), 354 BorrowKind::Unique => w!(self, "&uniq "), 355 BorrowKind::Mut { .. } => w!(self, "&mut "), 356 } 357 self.place(p); 358 } 359 Rvalue::Aggregate(AggregateKind::Tuple(_), x) => { 360 w!(self, "("); 361 self.operand_list(x); 362 w!(self, ")"); 363 } 364 Rvalue::Aggregate(AggregateKind::Array(_), x) => { 365 w!(self, "["); 366 self.operand_list(x); 367 w!(self, "]"); 368 } 369 Rvalue::Repeat(op, len) => { 370 w!(self, "["); 371 self.operand(op); 372 w!(self, "; {}]", len.display(self.db)); 373 } 374 Rvalue::Aggregate(AggregateKind::Adt(_, _), x) => { 375 w!(self, "Adt("); 376 self.operand_list(x); 377 w!(self, ")"); 378 } 379 Rvalue::Aggregate(AggregateKind::Closure(_), x) => { 380 w!(self, "Closure("); 381 self.operand_list(x); 382 w!(self, ")"); 383 } 384 Rvalue::Aggregate(AggregateKind::Union(_, _), x) => { 385 w!(self, "Union("); 386 self.operand_list(x); 387 w!(self, ")"); 388 } 389 Rvalue::Len(p) => { 390 w!(self, "Len("); 391 self.place(p); 392 w!(self, ")"); 393 } 394 Rvalue::Cast(ck, op, ty) => { 395 w!(self, "Cast({ck:?}, "); 396 self.operand(op); 397 w!(self, ", {})", self.hir_display(ty)); 398 } 399 Rvalue::CheckedBinaryOp(b, o1, o2) => { 400 self.operand(o1); 401 w!(self, " {b} "); 402 self.operand(o2); 403 } 404 Rvalue::UnaryOp(u, o) => { 405 let u = match u { 406 UnOp::Not => "!", 407 UnOp::Neg => "-", 408 }; 409 w!(self, "{u} "); 410 self.operand(o); 411 } 412 Rvalue::Discriminant(p) => { 413 w!(self, "Discriminant("); 414 self.place(p); 415 w!(self, ")"); 416 } 417 Rvalue::ShallowInitBoxWithAlloc(_) => w!(self, "ShallowInitBoxWithAlloc"), 418 Rvalue::ShallowInitBox(op, _) => { 419 w!(self, "ShallowInitBox("); 420 self.operand(op); 421 w!(self, ")"); 422 } 423 Rvalue::CopyForDeref(p) => { 424 w!(self, "CopyForDeref("); 425 self.place(p); 426 w!(self, ")"); 427 } 428 } 429 } 430 operand_list(&mut self, x: &[Operand])431 fn operand_list(&mut self, x: &[Operand]) { 432 let mut it = x.iter(); 433 if let Some(first) = it.next() { 434 self.operand(first); 435 for op in it { 436 w!(self, ", "); 437 self.operand(op); 438 } 439 } 440 } 441 hir_display<T: HirDisplay>(&self, ty: &'a T) -> impl Display + 'a442 fn hir_display<T: HirDisplay>(&self, ty: &'a T) -> impl Display + 'a { 443 ty.display(self.db).with_closure_style(ClosureStyle::ClosureWithSubst) 444 } 445 } 446