//! Text formatting for Slonik IR. //! //! Expressions that are never referenced by any statement are emitted in a //! trailing `// dead expressions` section. use std::collections::HashSet; use std::fmt; use crate::ir::{Block, BlockCall, Body, Expr, ExprData, Stmt, StmtData, Value, ValueDef}; /// Writes `body` in textual form. pub fn write_body(f: &mut fmt::Formatter<'_>, body: &Body) -> fmt::Result { let mut printer = Printer::new(body); printer.write_body(f) } impl fmt::Display for Body { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write_body(f, self) } } struct Printer<'a> { body: &'a Body, emitted: HashSet, visiting: HashSet, } impl<'a> Printer<'a> { fn new(body: &'a Body) -> Self { Self { body, emitted: HashSet::new(), visiting: HashSet::new(), } } fn write_body(&mut self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "body {{")?; let mut first_block = true; for block in self.body.blocks() { if !first_block { writeln!(f)?; } first_block = false; self.write_block(f, block)?; } let mut wrote_dead_header = false; for (expr, node) in self.body.exprs.iter() { let value = node.value; if !self.emitted.contains(&value) { if !wrote_dead_header { if !first_block { writeln!(f)?; } writeln!(f, " // dead expressions")?; wrote_dead_header = true; } self.emit_expr(f, expr, 1)?; } } writeln!(f, "}}") } fn write_block(&mut self, f: &mut fmt::Formatter<'_>, block: Block) -> fmt::Result { self.write_block_header(f, block)?; for &stmt in self.body.block_stmts(block) { self.emit_stmt_deps(f, stmt, 2)?; self.write_stmt(f, stmt, 2)?; } Ok(()) } fn write_block_header(&mut self, f: &mut fmt::Formatter<'_>, block: Block) -> fmt::Result { write!(f, " ^{}", block)?; let params = self.body.block_params(block); if !params.is_empty() { write!(f, "(")?; for (i, ¶m) in params.iter().enumerate() { if i != 0 { write!(f, ", ")?; } let ty = self.body.value_type(param); write!(f, "%{}: {}", param, ty)?; } write!(f, ")")?; } writeln!(f, ":") } fn emit_stmt_deps( &mut self, f: &mut fmt::Formatter<'_>, stmt: Stmt, indent: usize, ) -> fmt::Result { match self.body.stmt_data(stmt) { StmtData::Store { addr, value, .. } => { self.emit_value(f, *addr, indent)?; self.emit_value(f, *value, indent)?; } StmtData::Call { callee, args } => { self.emit_value(f, *callee, indent)?; for &arg in args.as_slice(&self.body.value_lists) { self.emit_value(f, arg, indent)?; } } StmtData::Jump { dst } => { self.emit_block_call_args(f, *dst, indent)?; } StmtData::BrIf { cond, then_dst, else_dst, } => { self.emit_value(f, *cond, indent)?; self.emit_block_call_args(f, *then_dst, indent)?; self.emit_block_call_args(f, *else_dst, indent)?; } StmtData::Return { values } => { for &value in values.as_slice(&self.body.value_lists) { self.emit_value(f, value, indent)?; } } } Ok(()) } fn emit_block_call_args( &mut self, f: &mut fmt::Formatter<'_>, call: BlockCall, indent: usize, ) -> fmt::Result { for &arg in call.args.as_slice(&self.body.value_lists) { self.emit_value(f, arg, indent)?; } Ok(()) } fn emit_value( &mut self, f: &mut fmt::Formatter<'_>, value: Value, indent: usize, ) -> fmt::Result { if self.emitted.contains(&value) { return Ok(()); } match self.body.value_def(value) { ValueDef::Param(_, _) => Ok(()), ValueDef::Expr(expr) => self.emit_expr(f, expr, indent), } } fn emit_expr(&mut self, f: &mut fmt::Formatter<'_>, expr: Expr, indent: usize) -> fmt::Result { let value = self.body.expr_value(expr); if self.emitted.contains(&value) { return Ok(()); } if !self.visiting.insert(expr) { return Err(fmt::Error); } match self.body.expr_data(expr) { ExprData::IConst { .. } | ExprData::F32Const { .. } | ExprData::F64Const { .. } | ExprData::BConst { .. } => {} ExprData::Unary { arg, .. } | ExprData::Cast { arg, .. } => { self.emit_value(f, *arg, indent)?; } ExprData::Binary { lhs, rhs, .. } | ExprData::Icmp { lhs, rhs, .. } | ExprData::Fcmp { lhs, rhs, .. } => { self.emit_value(f, *lhs, indent)?; self.emit_value(f, *rhs, indent)?; } ExprData::Select { cond, if_true, if_false, } => { self.emit_value(f, *cond, indent)?; self.emit_value(f, *if_true, indent)?; self.emit_value(f, *if_false, indent)?; } ExprData::Load { addr, .. } => { self.emit_value(f, *addr, indent)?; } } self.write_indent(f, indent)?; self.write_expr_binding(f, expr)?; self.visiting.remove(&expr); self.emitted.insert(value); Ok(()) } fn write_expr_binding(&mut self, f: &mut fmt::Formatter<'_>, expr: Expr) -> fmt::Result { let value = self.body.expr_value(expr); let ty = self.body.value_type(value); write!(f, "%{} = ", value)?; match self.body.expr_data(expr) { ExprData::IConst { imm } => { write!(f, "iconst {}", imm)?; writeln!(f, " : {}", ty) } ExprData::F32Const { bits } => { write!(f, "f32const 0x{bits:08x}")?; writeln!(f, " : {}", ty) } ExprData::F64Const { bits } => { write!(f, "f64const 0x{bits:016x}")?; writeln!(f, " : {}", ty) } ExprData::BConst { value } => { write!(f, "bconst {}", value)?; writeln!(f, " : {}", ty) } ExprData::Unary { op, arg } => { write!(f, "{} %{}", op, arg)?; writeln!(f, " : {}", ty) } ExprData::Binary { op, lhs, rhs } => { write!(f, "{} %{}, %{}", op, lhs, rhs)?; writeln!(f, " : {}", ty) } ExprData::Cast { op, arg } => { let src_ty = self.body.value_type(*arg); writeln!(f, "{} %{} : {} -> {}", op, arg, src_ty, ty) } ExprData::Icmp { cc, lhs, rhs } => { let operand_ty = self.body.value_type(*lhs); writeln!(f, "icmp {} %{}, %{} : {}", cc, lhs, rhs, operand_ty) } ExprData::Fcmp { cc, lhs, rhs } => { let operand_ty = self.body.value_type(*lhs); writeln!(f, "fcmp {} %{}, %{} : {}", cc, lhs, rhs, operand_ty) } ExprData::Select { cond, if_true, if_false, } => { write!(f, "select %{}, %{}, %{}", cond, if_true, if_false)?; writeln!(f, " : {}", ty) } ExprData::Load { addr, size } => { writeln!(f, "load %{} : {} -> {}", addr, size, ty) } } } fn write_stmt(&mut self, f: &mut fmt::Formatter<'_>, stmt: Stmt, indent: usize) -> fmt::Result { self.write_indent(f, indent)?; match self.body.stmt_data(stmt) { StmtData::Store { addr, value, size } => { writeln!(f, "store %{}, %{} : {}", value, addr, size) } StmtData::Call { callee, args } => { write!(f, "call %{}", callee)?; if !args.is_empty() { write!(f, "(")?; self.write_value_list(f, args.as_slice(&self.body.value_lists))?; writeln!(f, ")")?; } Ok(()) } StmtData::Jump { dst } => { write!(f, "br ")?; self.write_block_call(f, *dst)?; writeln!(f) } StmtData::BrIf { cond, then_dst, else_dst, } => { write!(f, "cond_br %{}, ", cond)?; self.write_block_call(f, *then_dst)?; write!(f, ", ")?; self.write_block_call(f, *else_dst)?; writeln!(f) } StmtData::Return { values } => { write!(f, "return")?; let values = values.as_slice(&self.body.value_lists); if !values.is_empty() { write!(f, " ")?; self.write_value_list(f, values)?; } writeln!(f) } } } fn write_block_call(&mut self, f: &mut fmt::Formatter<'_>, call: BlockCall) -> fmt::Result { write!(f, "^{}", call.block)?; if !call.args.is_empty() { write!(f, "(")?; self.write_value_list(f, call.args.as_slice(&self.body.value_lists))?; write!(f, ")")?; } Ok(()) } fn write_value_list(&mut self, f: &mut fmt::Formatter<'_>, values: &[Value]) -> fmt::Result { for (i, value) in values.iter().enumerate() { if i != 0 { write!(f, ", ")?; } write!(f, "%{}", value)?; } Ok(()) } fn write_indent(&mut self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { for _ in 0..indent { write!(f, " ")?; } Ok(()) } }