363 lines
10 KiB
Rust
363 lines
10 KiB
Rust
//! 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<Value>,
|
|
visiting: HashSet<Expr>,
|
|
}
|
|
|
|
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(())
|
|
}
|
|
}
|