diff --git a/crates/codegen/src/critical_edge.rs b/crates/codegen/src/critical_edge.rs index 2d46e3da..484813e0 100644 --- a/crates/codegen/src/critical_edge.rs +++ b/crates/codegen/src/critical_edge.rs @@ -1,4 +1,4 @@ -use super::cfg::ControlFlowGraph; +use sonatina_ir::ControlFlowGraph; use sonatina_ir::{ func_cursor::{CursorLocation, FuncCursor, InsnInserter}, diff --git a/crates/codegen/src/domtree.rs b/crates/codegen/src/domtree.rs index b08e458b..9b23c32e 100644 --- a/crates/codegen/src/domtree.rs +++ b/crates/codegen/src/domtree.rs @@ -7,9 +7,7 @@ use std::collections::BTreeSet; use cranelift_entity::{packed_option::PackedOption, SecondaryMap}; -use sonatina_ir::Block; - -use super::cfg::ControlFlowGraph; +use sonatina_ir::{Block, ControlFlowGraph}; #[derive(Default, Debug)] pub struct DomTree { diff --git a/crates/codegen/src/lib.rs b/crates/codegen/src/lib.rs index 2bdd2419..eaaa2a70 100644 --- a/crates/codegen/src/lib.rs +++ b/crates/codegen/src/lib.rs @@ -2,7 +2,6 @@ // See and #![allow(clippy::needless_collect)] -pub mod cfg; pub mod critical_edge; pub mod domtree; pub mod loop_analysis; diff --git a/crates/codegen/src/loop_analysis.rs b/crates/codegen/src/loop_analysis.rs index 99dfde9e..0adbbc0b 100644 --- a/crates/codegen/src/loop_analysis.rs +++ b/crates/codegen/src/loop_analysis.rs @@ -2,9 +2,9 @@ use cranelift_entity::{entity_impl, packed_option::PackedOption, PrimaryMap, Sec use fxhash::FxHashMap; use smallvec::SmallVec; -use crate::{cfg::ControlFlowGraph, domtree::DomTree}; +use crate::domtree::DomTree; -use sonatina_ir::Block; +use sonatina_ir::{Block, ControlFlowGraph}; #[derive(Debug, Default)] pub struct LoopTree { diff --git a/crates/codegen/src/optim/gvn.rs b/crates/codegen/src/optim/gvn.rs index d91e449e..7b128a27 100644 --- a/crates/codegen/src/optim/gvn.rs +++ b/crates/codegen/src/optim/gvn.rs @@ -12,15 +12,12 @@ use std::collections::BTreeSet; use cranelift_entity::{entity_impl, packed_option::PackedOption, PrimaryMap, SecondaryMap}; use fxhash::{FxHashMap, FxHashSet}; -use crate::{ - cfg::ControlFlowGraph, - domtree::{DomTree, DominatorTreeTraversable}, -}; +use crate::domtree::{DomTree, DominatorTreeTraversable}; use sonatina_ir::{ func_cursor::{CursorLocation, FuncCursor, InsnInserter}, insn::{BinaryOp, CastOp, InsnData, UnaryOp}, - Block, DataFlowGraph, Function, Immediate, Insn, Type, Value, + Block, ControlFlowGraph, DataFlowGraph, Function, Immediate, Insn, Type, Value, }; use super::{constant_folding, simplify_impl}; diff --git a/crates/codegen/src/optim/licm.rs b/crates/codegen/src/optim/licm.rs index a8892eed..c21ee067 100644 --- a/crates/codegen/src/optim/licm.rs +++ b/crates/codegen/src/optim/licm.rs @@ -1,14 +1,11 @@ // TODO: Add control flow hoisting. use fxhash::{FxHashMap, FxHashSet}; -use crate::{ - cfg::ControlFlowGraph, - loop_analysis::{Loop, LoopTree}, -}; +use crate::loop_analysis::{Loop, LoopTree}; use sonatina_ir::{ func_cursor::{CursorLocation, FuncCursor, InsnInserter}, - Block, Function, Insn, InsnData, Value, + Block, ControlFlowGraph, Function, Insn, InsnData, Value, }; #[derive(Debug)] diff --git a/crates/codegen/src/optim/sccp.rs b/crates/codegen/src/optim/sccp.rs index 157fee64..75c72f55 100644 --- a/crates/codegen/src/optim/sccp.rs +++ b/crates/codegen/src/optim/sccp.rs @@ -8,12 +8,10 @@ use std::{collections::BTreeSet, ops}; use cranelift_entity::SecondaryMap; -use crate::cfg::ControlFlowGraph; - use sonatina_ir::{ func_cursor::{CursorLocation, FuncCursor, InsnInserter}, insn::{BinaryOp, CastOp, InsnData, UnaryOp}, - Block, Function, Immediate, Insn, Type, Value, + Block, ControlFlowGraph, Function, Immediate, Insn, Type, Value, }; #[derive(Debug)] diff --git a/crates/codegen/src/post_domtree.rs b/crates/codegen/src/post_domtree.rs index a2cf0690..9ca59322 100644 --- a/crates/codegen/src/post_domtree.rs +++ b/crates/codegen/src/post_domtree.rs @@ -1,11 +1,8 @@ //! This module contains implementation of `Post Dominator Tree`. -use super::{ - cfg::ControlFlowGraph, - domtree::{DFSet, DomTree}, -}; +use super::domtree::{DFSet, DomTree}; -use sonatina_ir::{Block, Function}; +use sonatina_ir::{Block, ControlFlowGraph, Function}; #[derive(Debug)] pub struct PostDomTree { diff --git a/crates/filecheck/src/gvn.rs b/crates/filecheck/src/gvn.rs index 256de160..a798a875 100644 --- a/crates/filecheck/src/gvn.rs +++ b/crates/filecheck/src/gvn.rs @@ -1,8 +1,8 @@ use std::path::{Path, PathBuf}; -use sonatina_codegen::{cfg::ControlFlowGraph, domtree::DomTree, optim::gvn::GvnSolver}; +use sonatina_codegen::{domtree::DomTree, optim::gvn::GvnSolver}; -use sonatina_ir::Function; +use sonatina_ir::{ControlFlowGraph, Function}; use super::{FuncTransform, FIXTURE_ROOT}; diff --git a/crates/filecheck/src/licm.rs b/crates/filecheck/src/licm.rs index f7e32266..a48fb08c 100644 --- a/crates/filecheck/src/licm.rs +++ b/crates/filecheck/src/licm.rs @@ -1,10 +1,8 @@ use std::path::{Path, PathBuf}; -use sonatina_codegen::{ - cfg::ControlFlowGraph, domtree::DomTree, loop_analysis::LoopTree, optim::licm::LicmSolver, -}; +use sonatina_codegen::{domtree::DomTree, loop_analysis::LoopTree, optim::licm::LicmSolver}; -use sonatina_ir::Function; +use sonatina_ir::{ControlFlowGraph, Function}; use super::{FuncTransform, FIXTURE_ROOT}; diff --git a/crates/filecheck/src/sccp.rs b/crates/filecheck/src/sccp.rs index ad31636c..dc9c9ff4 100644 --- a/crates/filecheck/src/sccp.rs +++ b/crates/filecheck/src/sccp.rs @@ -1,8 +1,8 @@ use std::path::{Path, PathBuf}; -use sonatina_codegen::{cfg::ControlFlowGraph, optim::sccp::SccpSolver}; +use sonatina_codegen::optim::sccp::SccpSolver; -use sonatina_ir::Function; +use sonatina_ir::{ControlFlowGraph, Function}; use super::{FuncTransform, FIXTURE_ROOT}; diff --git a/crates/ir/Cargo.toml b/crates/ir/Cargo.toml index d4279f27..92543f43 100644 --- a/crates/ir/Cargo.toml +++ b/crates/ir/Cargo.toml @@ -21,3 +21,4 @@ fxhash = "0.2.1" dyn-clone = "1.0.4" sonatina-triple = { path = "../triple", version = "0.0.3-alpha" } indexmap = "2.0.0" +dot2 = { git = "https://github.com/sanpii/dot2.rs.git" } diff --git a/crates/codegen/src/cfg.rs b/crates/ir/src/cfg.rs similarity index 96% rename from crates/codegen/src/cfg.rs rename to crates/ir/src/cfg.rs index d4e5687d..cd9c5fea 100644 --- a/crates/codegen/src/cfg.rs +++ b/crates/ir/src/cfg.rs @@ -2,13 +2,13 @@ use std::collections::BTreeSet; use cranelift_entity::{packed_option::PackedOption, SecondaryMap}; -use sonatina_ir::{Block, Function, Insn}; +use crate::{Block, Function, Insn}; #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct ControlFlowGraph { entry: PackedOption, blocks: SecondaryMap, - pub(super) exits: smallvec::SmallVec<[Block; 8]>, + pub exits: smallvec::SmallVec<[Block; 8]>, } impl ControlFlowGraph { @@ -62,7 +62,7 @@ impl ControlFlowGraph { self.blocks[from].remove_succ(to); } - pub(super) fn reverse_edges(&mut self, new_entry: Block, new_exits: &[Block]) { + pub fn reverse_edges(&mut self, new_entry: Block, new_exits: &[Block]) { for node in self.blocks.values_mut() { node.reverse_edge(); } diff --git a/crates/ir/src/dfg.rs b/crates/ir/src/dfg.rs index 0a9b0218..6da96eea 100644 --- a/crates/ir/src/dfg.rs +++ b/crates/ir/src/dfg.rs @@ -1,8 +1,8 @@ //! This module contains Sonatine IR data flow graph. +use std::{collections::BTreeSet, fmt}; use cranelift_entity::{packed_option::PackedOption, PrimaryMap, SecondaryMap}; use fxhash::FxHashMap; -use std::collections::BTreeSet; use crate::{global_variable::ConstantValue, module::ModuleCtx, GlobalVariable}; @@ -308,6 +308,12 @@ pub enum ValueDef { pub struct Block(pub u32); cranelift_entity::entity_impl!(Block); +impl fmt::Display for Block { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "block{}", self.0) + } +} + /// A block data definition. /// A Block data doesn't hold any information for layout of a program. It is managed by /// [`super::layout::Layout`]. diff --git a/crates/ir/src/function.rs b/crates/ir/src/function.rs index 9e269df3..ca8d5ac4 100644 --- a/crates/ir/src/function.rs +++ b/crates/ir/src/function.rs @@ -1,7 +1,9 @@ +use std::fmt::{self, Write}; + use fxhash::FxHashMap; use smallvec::SmallVec; -use crate::{module::ModuleCtx, Linkage}; +use crate::{module::ModuleCtx, types::DisplayType, Linkage}; use super::{module::FuncRef, DataFlowGraph, Layout, Type, Value}; @@ -87,3 +89,37 @@ impl Signature { self.ret_ty = ty; } } + +pub struct DisplaySignature<'a> { + sig: &'a Signature, + dfg: &'a DataFlowGraph, +} + +impl<'a> DisplaySignature<'a> { + pub fn new(sig: &'a Signature, dfg: &'a DataFlowGraph) -> Self { + Self { sig, dfg } + } +} + +impl<'a> fmt::Display for DisplaySignature<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { sig, dfg } = *self; + let Signature { + name, + linkage, + args, + ret_ty, + } = sig; + + let mut args_ty = String::new(); + for arg_ty in args { + let ty = DisplayType::new(*arg_ty, dfg); + write!(&mut args_ty, "{ty} ")?; + } + let args_ty = args_ty.trim(); + + let ret_ty = DisplayType::new(*ret_ty, dfg); + + write!(f, "func {linkage} %{name}({args_ty}) -> {ret_ty}") + } +} diff --git a/crates/ir/src/graphviz/block.rs b/crates/ir/src/graphviz/block.rs new file mode 100644 index 00000000..3a3335a9 --- /dev/null +++ b/crates/ir/src/graphviz/block.rs @@ -0,0 +1,65 @@ +use std::fmt::Write; + +use dot2::label; + +use crate::{function::DisplaySignature, insn::DisplayInsn, Block, ControlFlowGraph, Function}; + +use super::function::DUMMY_BLOCK; + +#[derive(Debug, Clone, Copy)] +pub(super) struct BlockNode<'a> { + pub(super) func: &'a Function, + pub(super) cfg: &'a ControlFlowGraph, + pub(super) block: Block, +} + +impl<'a> BlockNode<'a> { + pub(super) fn new(func: &'a Function, cfg: &'a ControlFlowGraph, block: Block) -> Self { + Self { func, cfg, block } + } + + pub(super) fn succs(self) -> Vec { + self.cfg.succs_of(self.block) + .map(|block| BlockNode::new(self.func, self.cfg, *block)) + .collect() + } +} + +impl<'a> BlockNode<'a> { + pub(super) fn label(self) -> label::Text<'static> { + let Self { block, func, .. } = self; + let Function { + sig, dfg, layout, .. + } = func; + if block == DUMMY_BLOCK { + let sig = DisplaySignature::new(sig, dfg); + return label::Text::LabelStr(format!("{sig}").into()); + } + + let mut label = r#""#.to_string(); + + // Write block header. + write!( + &mut label, + r#""#, + block + ) + .unwrap(); + + // Write block body. + write!(label, r#""#).unwrap(); + + write!(label, "
{}
"#).unwrap(); + for insn in layout.iter_insn(self.block) { + let display_insn = DisplayInsn::new(insn, func); + let mut insn_string = String::new(); + write!(&mut insn_string, "{}", display_insn).unwrap(); + + write!(label, "{}", dot2::escape_html(&insn_string)).unwrap(); + write!(label, "
").unwrap(); + } + write!(label, r#"
").unwrap(); + + label::Text::HtmlStr(label.into()) + } +} diff --git a/crates/ir/src/graphviz/function.rs b/crates/ir/src/graphviz/function.rs new file mode 100644 index 00000000..2880504b --- /dev/null +++ b/crates/ir/src/graphviz/function.rs @@ -0,0 +1,141 @@ +use std::iter; + +use dot2::{label::Text, GraphWalk, Id, Labeller, Style}; + +use crate::{value::DisplayArgValues, Block, ControlFlowGraph, Function, InsnData}; + +use super::block::BlockNode; + +pub(super) const DUMMY_BLOCK: Block = Block(u32::MAX); + +pub(super) struct FunctionGraph<'a> { + func: &'a Function, + cfg: &'a ControlFlowGraph, +} + +impl<'a> FunctionGraph<'a> { + pub fn new(func: &'a Function, cfg: &'a ControlFlowGraph) -> Self { + Self { func, cfg } + } +} + +impl<'a> FunctionGraph<'a> { + pub(super) fn blocks(&self) -> Vec> { + let Self { func, cfg } = self; + // Dummy block is needed to label the graph with the function signature. Returns a vector + // with the dummy block as a last element. + cfg.post_order() + .map(|block| BlockNode::new(func, cfg, block)) + .chain(iter::once(BlockNode::new(func, cfg, DUMMY_BLOCK))) + .collect() + } +} + +impl<'a> Labeller<'a> for FunctionGraph<'a> { + type Node = BlockNode<'a>; + type Edge = BlockEdge<'a>; + type Subgraph = (); + + fn graph_id(&self) -> dot2::Result> { + let func = self.func; + let sig_name = func.sig.name().to_string(); + Id::new(sig_name) + } + + fn node_id(&self, n: &Self::Node) -> dot2::Result> { + let block = n.block; + if block == DUMMY_BLOCK { + return dot2::Id::new("dummy_block"); + } + dot2::Id::new(format!("{block}")) + } + + fn node_shape(&self, _n: &Self::Node) -> Option> { + Some(Text::LabelStr("none".into())) + } + + fn edge_style(&'a self, e: &Self::Edge) -> Style { + if e.from.block == DUMMY_BLOCK { + Style::Invisible + } else { + Style::None + } + } + + fn node_label(&'a self, n: &Self::Node) -> dot2::Result> { + Ok(n.label()) + } + + fn edge_label(&self, e: &Self::Edge) -> Text<'a> { + e.label() + } +} + +impl<'a> GraphWalk<'a> for FunctionGraph<'a> { + type Node = BlockNode<'a>; + type Edge = BlockEdge<'a>; + type Subgraph = (); + + fn nodes(&self) -> dot2::Nodes<'a, Self::Node> { + self.blocks().into() + } + + fn edges(&'a self) -> dot2::Edges<'a, Self::Edge> { + let Self { func, cfg } = self; + let mut blocks = self.blocks(); + + let dummy_block = blocks.pop().unwrap(); + let mut edges = vec![BlockEdge { + from: dummy_block, + to: BlockNode::new(func, cfg, Block(0u32)), + func, + }]; + for block in blocks { + for succ in block.succs() { + let edge = BlockEdge { + from: block, + to: succ, + func, + }; + edges.push(edge); + } + } + + edges.into() + } + + fn source(&self, edge: &Self::Edge) -> Self::Node { + edge.from + } + + fn target(&self, edge: &Self::Edge) -> Self::Node { + edge.to + } +} + +#[derive(Debug, Clone, Copy)] +pub(super) struct BlockEdge<'a> { + from: BlockNode<'a>, + to: BlockNode<'a>, + func: &'a Function, +} + +impl<'a> BlockEdge<'a> { + fn label(self) -> Text<'static> { + let Self { from, to, func } = self; + let to = to.block; + let from = from.block; + for insn in func.layout.iter_insn(to) { + if let InsnData::Phi { values, blocks, .. } = func.dfg.insn_data(insn) { + for (i, block) in blocks.into_iter().enumerate() { + if *block == from { + let flow_arg = [values[i]]; + let v = DisplayArgValues::new(&flow_arg, &func.dfg); + return Text::LabelStr(format!("{v}").into()); + } + } + } + } + Text::LabelStr("".into()) + } +} diff --git a/crates/ir/src/graphviz/mod.rs b/crates/ir/src/graphviz/mod.rs new file mode 100644 index 00000000..8f4fb272 --- /dev/null +++ b/crates/ir/src/graphviz/mod.rs @@ -0,0 +1,78 @@ +use std::io; + +use crate::{ControlFlowGraph, Function}; + +mod block; +mod function; + +use function::FunctionGraph; + +pub fn render_to(func: &Function, output: &mut W) -> io::Result<()> { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(func); + let func_graph = FunctionGraph::new(func, &cfg); + dot2::render(&func_graph, output).map_err(|err| match err { + dot2::Error::Io(err) => err, + _ => panic!("invalid graphviz id"), + }) +} + +#[cfg(test)] +mod test { + use crate::{builder, Type}; + + use super::*; + + #[test] + fn test_dump_ir() { + let mut test_module_builder = builder::test_util::TestModuleBuilder::new(); + let mut builder = test_module_builder.func_builder(&[Type::I64], Type::Void); + + let entry_block = builder.append_block(); + let then_block = builder.append_block(); + let else_block = builder.append_block(); + let merge_block = builder.append_block(); + + let arg0 = builder.args()[0]; + + builder.switch_to_block(entry_block); + builder.br(arg0, then_block, else_block); + + builder.switch_to_block(then_block); + let v1 = builder.make_imm_value(1i64); + builder.jump(merge_block); + + builder.switch_to_block(else_block); + let v2 = builder.make_imm_value(2i64); + builder.jump(merge_block); + + builder.switch_to_block(merge_block); + let v3 = builder.phi(&[(v1, then_block), (v2, else_block)]); + builder.add(v3, arg0); + builder.ret(None); + + builder.seal_all(); + let func_ref = builder.finish(); + let module = test_module_builder.build(); + + let mut text = vec![]; + render_to(&module.funcs[func_ref], &mut text).unwrap(); + + assert_eq!( + text, + b"digraph test_func { + block3[label=<
block3
v3.i64 = phi (1.i64 block1) (2.i64 block2);
v4.i64 = add v3 v0;
ret;
>][shape=\"none\"]; + block2[label=<
block2
jump block3;
>][shape=\"none\"]; + block1[label=<
block1
jump block3;
>][shape=\"none\"]; + block0[label=<
block0
branch v0 block1 block2;
>][shape=\"none\"]; + dummy_block[label=\"func public %test_func(i64) -> ()\"][shape=\"none\"]; + dummy_block -> block0[label=\"\"][style=\"invis\"]; + block2 -> block3[label=\"2.i64\"]; + block1 -> block3[label=\"1.i64\"]; + block0 -> block1[label=\"\"]; + block0 -> block2[label=\"\"]; +} +" + ); + } +} diff --git a/crates/ir/src/insn.rs b/crates/ir/src/insn.rs index 7596971e..c6f3c6de 100644 --- a/crates/ir/src/insn.rs +++ b/crates/ir/src/insn.rs @@ -5,15 +5,45 @@ use std::fmt; use smallvec::SmallVec; -use crate::types::CompoundTypeData; +use crate::{ + function::Function, + types::{CompoundTypeData, DisplayType}, + value::{DisplayArgValues, DisplayResultValue}, +}; -use super::{module::FuncRef, Block, DataFlowGraph, Type, Value, ValueData}; +use super::{ + module::{DisplayCalleeFuncRef, FuncRef}, + Block, DataFlowGraph, Type, Value, ValueData, +}; /// An opaque reference to [`InsnData`] #[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)] pub struct Insn(pub u32); cranelift_entity::entity_impl!(Insn); +pub struct DisplayInsn<'a> { + insn: Insn, + func: &'a Function, +} + +impl<'a> DisplayInsn<'a> { + pub fn new(insn: Insn, func: &'a Function) -> Self { + Self { insn, func } + } +} + +impl<'a> fmt::Display for DisplayInsn<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { insn, func } = *self; + + let result = DisplayResultValue::new(insn, &func.dfg); + write!(f, "{result}")?; + + let insn = DisplayInsnData::new(insn, func); + write!(f, "{insn}") + } +} + /// An instruction data definition. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum InsnData { @@ -105,6 +135,20 @@ pub enum DataLocationKind { Storage, } +impl fmt::Display for DataLocationKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use DataLocationKind::*; + write!( + f, + "{}", + match self { + Memory => "mem", + Storage => "store", + } + ) + } +} + impl InsnData { pub fn unary(code: UnaryOp, lhs: Value) -> Self { Self::Unary { code, args: [lhs] } @@ -376,6 +420,103 @@ impl InsnData { } } +pub struct DisplayInsnData<'a> { + insn: Insn, + func: &'a Function, +} + +impl<'a> DisplayInsnData<'a> { + pub fn new(insn: Insn, func: &'a Function) -> Self { + Self { insn, func } + } +} + +impl<'a> fmt::Display for DisplayInsnData<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use InsnData::*; + let Self { insn, func } = *self; + let dfg = &func.dfg; + let insn_data = dfg.insn_data(insn); + match insn_data { + Unary { code, args } => { + let v = DisplayArgValues::new(args, dfg); + write!(f, "{} {v};", code.as_str()) + } + Binary { code, args } => { + let v = DisplayArgValues::new(args, dfg); + write!(f, "{} {v};", code.as_str(),) + } + Cast { code, args, .. } => { + let v = DisplayArgValues::new(args, dfg); + write!(f, "{} {v};", code.as_str()) + } + Load { args, loc } => { + let v = DisplayArgValues::new(args, dfg); + write!(f, "{loc} load {v};") + } + Store { args, loc } => { + let v = DisplayArgValues::new(args, dfg); + write!(f, "store {loc} {v};") + } + Call { + args, func: callee, .. + } => { + let v = DisplayArgValues::new(args, dfg); + let callee = DisplayCalleeFuncRef::new(callee, func); + write!(f, "call %{callee} {v};") + } + Jump { code, dests } => { + let block = dests[0]; + write!(f, "{code} {block};") + } + Branch { args, dests } => { + let v = DisplayArgValues::new(args, dfg); + write!(f, "branch {v} {} {};", dests[0], dests[1]) + } + BrTable { + args, + default, + table, + } => { + let v = DisplayArgValues::new(args, dfg); + write!(f, "br_table {v}")?; + if let Some(block) = default { + write!(f, " {block}")?; + } + for block in &table[..table.len() - 2] { + write!(f, " {block}")?; + } + write!(f, " {};", table[table.len() - 1]) + } + Alloca { ty } => { + let ty = DisplayType::new(*ty, dfg); + write!(f, "alloca {ty};") + } + Return { args } => { + write!(f, "ret")?; + if let Some(arg) = args { + let arg = [*arg]; + let v = DisplayArgValues::new(&arg, dfg); + write!(f, " {v}")?; + } + write!(f, ";") + } + Gep { args } => { + let v = DisplayArgValues::new(args, dfg); + write!(f, "gep {v};") + } + Phi { values, blocks, .. } => { + write!(f, "phi")?; + for (value, block) in values.iter().zip(blocks.iter()) { + let value = [*value]; + let v = DisplayArgValues::new(&value, dfg); + write!(f, " ({v} {block})")?; + } + write!(f, ";") + } + } + } +} /// Unary operations. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum UnaryOp { diff --git a/crates/ir/src/lib.rs b/crates/ir/src/lib.rs index ae03029a..d36813f2 100644 --- a/crates/ir/src/lib.rs +++ b/crates/ir/src/lib.rs @@ -1,8 +1,10 @@ pub mod builder; +pub mod cfg; pub mod dfg; pub mod func_cursor; pub mod function; pub mod global_variable; +pub mod graphviz; pub mod insn; pub mod ir_writer; pub mod isa; @@ -16,9 +18,11 @@ mod bigint; pub use bigint::{I256, U256}; pub use builder::Variable; +pub use cfg::ControlFlowGraph; pub use dfg::{Block, BlockData, DataFlowGraph}; pub use function::{Function, Signature}; pub use global_variable::{GlobalVariable, GlobalVariableData}; +pub use graphviz::render_to; pub use insn::{BranchInfo, DataLocationKind, Insn, InsnData}; pub use layout::Layout; pub use linkage::Linkage; diff --git a/crates/ir/src/module.rs b/crates/ir/src/module.rs index 24f0c98c..c9bef630 100644 --- a/crates/ir/src/module.rs +++ b/crates/ir/src/module.rs @@ -1,4 +1,7 @@ -use std::sync::{Arc, Mutex}; +use std::{ + fmt, + sync::{Arc, Mutex}, +}; use cranelift_entity::{entity_impl, PrimaryMap}; @@ -86,3 +89,22 @@ impl ModuleCtx { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct FuncRef(u32); entity_impl!(FuncRef); + +pub struct DisplayCalleeFuncRef<'a> { + callee: &'a FuncRef, + func: &'a Function, +} + +impl<'a> DisplayCalleeFuncRef<'a> { + pub fn new(callee: &'a FuncRef, func: &'a Function) -> Self { + Self { callee, func } + } +} + +impl<'a> fmt::Display for DisplayCalleeFuncRef<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { callee, func } = *self; + let sig = func.callees.get(callee).unwrap(); + write!(f, "{}", sig.name()) + } +} diff --git a/crates/ir/src/types.rs b/crates/ir/src/types.rs index f83d2c9e..5bc3186c 100644 --- a/crates/ir/src/types.rs +++ b/crates/ir/src/types.rs @@ -1,11 +1,12 @@ //! This module contains Sonatina IR types definitions. - -use std::cmp; +use std::{cmp, fmt}; use cranelift_entity::PrimaryMap; use fxhash::FxHashMap; use indexmap::IndexMap; +use crate::DataFlowGraph; + #[derive(Debug, Default)] pub struct TypeStore { compounds: PrimaryMap, @@ -138,6 +139,67 @@ pub enum Type { pub struct CompoundType(u32); cranelift_entity::entity_impl!(CompoundType); +struct DisplayCompoundType<'a> { + cmpd_ty: CompoundType, + dfg: &'a DataFlowGraph, +} + +impl<'a> fmt::Display for DisplayCompoundType<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use CompoundTypeData::*; + let dfg = self.dfg; + dfg.ctx + .with_ty_store(|s| match s.resolve_compound(self.cmpd_ty) { + Array { elem: ty, len } => { + let ty = DisplayType::new(*ty, dfg); + write!(f, "[{ty};{len}]") + } + Ptr(ty) => { + let ty = DisplayType::new(*ty, dfg); + write!(f, "*{ty}") + } + Struct(StructData { name, packed, .. }) => { + if *packed { + write!(f, "<{{{name}}}>") + } else { + write!(f, "{{{name}}}") + } + } + }) + } +} + +pub struct DisplayType<'a> { + ty: Type, + dfg: &'a DataFlowGraph, +} + +impl<'a> DisplayType<'a> { + pub fn new(ty: Type, dfg: &'a DataFlowGraph) -> Self { + Self { ty, dfg } + } +} + +impl<'a> fmt::Display for DisplayType<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Type::*; + match self.ty { + I1 => write!(f, "i1"), + I8 => write!(f, "i8"), + I16 => write!(f, "i16"), + I32 => write!(f, "i32"), + I64 => write!(f, "i64"), + I128 => write!(f, "i128"), + I256 => write!(f, "i256"), + Compound(cmpd_ty) => { + let dfg = self.dfg; + write!(f, "{}", DisplayCompoundType { cmpd_ty, dfg }) + } + Void => write!(f, "()"), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CompoundTypeData { Array { elem: Type, len: usize }, diff --git a/crates/ir/src/value.rs b/crates/ir/src/value.rs index 5f106a0e..231764ab 100644 --- a/crates/ir/src/value.rs +++ b/crates/ir/src/value.rs @@ -2,7 +2,7 @@ use std::{fmt, ops}; -use crate::GlobalVariable; +use crate::{types::DisplayType, DataFlowGraph, GlobalVariable}; use super::{Insn, Type, I256, U256}; @@ -11,6 +11,62 @@ use super::{Insn, Type, I256, U256}; pub struct Value(pub u32); cranelift_entity::entity_impl!(Value); +pub struct DisplayResultValue<'a> { + insn: Insn, + dfg: &'a DataFlowGraph, +} + +impl<'a> DisplayResultValue<'a> { + pub fn new(insn: Insn, dfg: &'a DataFlowGraph) -> Self { + Self { insn, dfg } + } +} + +impl<'a> fmt::Display for DisplayResultValue<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { insn, dfg } = *self; + if let Some(value) = dfg.insn_result(insn) { + let ty = dfg.insn_result_ty(insn).unwrap(); + let ty = DisplayType::new(ty, dfg); + return write!(f, "v{}.{ty} = ", value.0); + } + Ok(()) + } +} + +pub struct DisplayArgValues<'a, 'b> { + args: &'a [Value], + dfg: &'b DataFlowGraph, +} + +impl<'a, 'b> DisplayArgValues<'a, 'b> { + pub fn new(args: &'a [Value], dfg: &'b DataFlowGraph) -> Self { + Self { args, dfg } + } + + pub fn write_arg(&self, w: &mut W, arg: &Value) -> fmt::Result { + let dfg = self.dfg; + match *dfg.value_data(*arg) { + ValueData::Immediate { imm, ty } => { + let ty = DisplayType::new(ty, dfg); + write!(w, "{imm}.{ty}") + } + _ => write!(w, "v{}", arg.0), + } + } +} + +impl<'a, 'b> fmt::Display for DisplayArgValues<'a, 'b> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.write_arg(f, &self.args[0])?; + for arg in &self.args[1..] { + write!(f, " ")?; + self.write_arg(f, arg)?; + } + Ok(()) + } +} + /// An value data definition. #[derive(Debug, Clone)] pub enum ValueData {