Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9388e56
mk_graph: introduce GraphBuilder trait
0xh4ty Feb 24, 2026
4c3b096
d2: extract item traversal helper
0xh4ty Feb 24, 2026
6ecfc1e
d2: introduce D2Builder struct
0xh4ty Feb 24, 2026
ba097dd
d2: implement GraphBuilder for D2Builder
0xh4ty Feb 24, 2026
dc66682
mk_graph: add generic graph traversal
0xh4ty Mar 1, 2026
d063ffe
d2: wire GraphBuilder traversal behind flag
0xh4ty Mar 1, 2026
64bfeb1
mk_graph: make traversal independent of D2 formatting
0xh4ty Mar 1, 2026
cbbbe4a
mk_graph: add comments clarifying traversal responsibilities
0xh4ty Mar 1, 2026
795084c
mk_graph: extend GraphBuilder to pass statements, terminators, and ca…
0xh4ty Mar 2, 2026
2389583
d2: elide explicit lifetime in GraphBuilder impl
0xh4ty Mar 2, 2026
da51b89
mk_graph: introduce RenderedFunction, RenderedBlock and CallEdge stru…
0xh4ty Mar 5, 2026
096d49c
mk_graph: refactor traversal and builders to use RenderedFunction model
0xh4ty Mar 5, 2026
d617db4
mk_graph: add body hash to function IDs to avoid collisions
0xh4ty Mar 5, 2026
ff84ec7
d2: remove SMIR_D2_NEW flag and use new traversal by default
0xh4ty Mar 5, 2026
32c6a55
mk_graph: expand documentation for traversal and rendering structures
0xh4ty Mar 5, 2026
abe0acd
d2: fix build issues after rebase and address clippy warnings
0xh4ty Mar 5, 2026
6392101
mk_graph: remove is_local and add raw_stmts to RenderedBlock
0xh4ty Mar 5, 2026
4d383f0
mk_graph: remove raw MIR fields and drop lifetimes from rendered structs
0xh4ty Mar 23, 2026
d99317e
mk_graph: fix call edge collection and compute external functions
0xh4ty Mar 23, 2026
da9eabc
mk_graph: add new DOT backend behind env gate with GraphBuilder stubs
0xh4ty Mar 23, 2026
bbb4502
mk_graph: implement DOT renderer using GraphBuilder and refine traver…
0xh4ty May 4, 2026
633d48d
mk_graph: elide unnecessary lifetime in render_function
0xh4ty May 7, 2026
3f9d36e
mk_graph: centralize graph identity resolution in traversal
0xh4ty May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/mk_graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::printer::collect_smir;
pub mod context;
pub mod index;
pub mod output;
pub mod traverse;
pub mod util;

// Re-exports for convenience
Expand All @@ -27,7 +28,13 @@ pub use util::GraphLabelString;

/// Entry point to write the DOT file
pub fn emit_dotfile(tcx: TyCtxt<'_>) {
let smir_dot = collect_smir(tcx).to_dot_file();
let smir = collect_smir(tcx);

let smir_dot = if std::env::var("SMIR_DOT_NEW").is_ok() {
smir.to_dot_file_new()
} else {
smir.to_dot_file()
};

match mir_output_path(tcx, "smir.dot") {
OutputDest::Stdout => {
Expand All @@ -45,7 +52,9 @@ pub fn emit_dotfile(tcx: TyCtxt<'_>) {

/// Entry point to write the D2 file
pub fn emit_d2file(tcx: TyCtxt<'_>) {
let smir_d2 = collect_smir(tcx).to_d2_file();
let smir = collect_smir(tcx);

let smir_d2 = smir.to_d2_file();

match mir_output_path(tcx, "smir.d2") {
OutputDest::Stdout => {
Expand Down
225 changes: 104 additions & 121 deletions src/mk_graph/output/d2.rs
Original file line number Diff line number Diff line change
@@ -1,151 +1,134 @@
//! D2 diagram format output for MIR graphs.

use crate::compat::stable_mir;
use stable_mir::mir::TerminatorKind;

use crate::printer::SmirJson;
use crate::MonoItemKind;

use crate::mk_graph::context::GraphContext;
use crate::mk_graph::util::{
escape_d2, is_unqualified, name_lines, short_name, terminator_targets,
};
use crate::mk_graph::util::{escape_d2, short_name};

impl SmirJson {
/// Convert the MIR to D2 diagram format
pub fn to_d2_file(self) -> String {
let ctx = GraphContext::from_smir(&self);
let mut output = String::new();

output.push_str("direction: right\n\n");
render_d2_allocs_legend(&ctx, &mut output);

for item in self.items {
match item.mono_item_kind {
MonoItemKind::MonoItemFn { name, body, .. } => {
render_d2_function(&name, body.as_ref(), &ctx, &mut output);
}
MonoItemKind::MonoItemGlobalAsm { asm } => {
render_d2_asm(&asm, &mut output);
}
MonoItemKind::MonoItemStatic { name, .. } => {
render_d2_static(&name, &mut output);
}
}
}

output
}
}
use crate::mk_graph::traverse::render_graph;
use crate::mk_graph::traverse::{GraphBuilder, RenderedFunction};

// =============================================================================
// D2 Rendering Helpers
// D2 Builder
// =============================================================================

fn render_d2_allocs_legend(ctx: &GraphContext, out: &mut String) {
let legend_lines = ctx.allocs_legend_lines();

out.push_str("ALLOCS: {\n");
out.push_str(" style.fill: \"#ffffcc\"\n");
out.push_str(" style.stroke: \"#999999\"\n");
let legend_text = legend_lines
.iter()
.map(|s| escape_d2(s))
.collect::<Vec<_>>()
.join("\\n");
out.push_str(&format!(" label: \"{}\"\n", legend_text));
out.push_str("}\n\n");
pub struct D2Builder {
buf: String,
}

fn render_d2_function(
name: &str,
body: Option<&stable_mir::mir::Body>,
ctx: &GraphContext,
out: &mut String,
) {
let fn_id = short_name(name);
let display_name = escape_d2(&name_lines(name));

// Function container
out.push_str(&format!("{}: {{\n", fn_id));
out.push_str(&format!(" label: \"{}\"\n", display_name));
out.push_str(" style.fill: \"#e0e0ff\"\n");

if let Some(body) = body {
render_d2_blocks(body, ctx, out);
render_d2_block_edges(body, out);
impl D2Builder {
pub fn new() -> Self {
Self { buf: String::new() }
}
}

out.push_str("}\n\n");

// Call edges (must be outside the container)
if let Some(body) = body {
render_d2_call_edges(&fn_id, body, ctx, out);
impl Default for D2Builder {
fn default() -> Self {
Self::new()
}
}

fn render_d2_blocks(body: &stable_mir::mir::Body, ctx: &GraphContext, out: &mut String) {
for (idx, block) in body.blocks.iter().enumerate() {
let stmts: Vec<String> = block
.statements
impl GraphBuilder for D2Builder {
type Output = String;

fn begin_graph(&mut self, _name: &str) {
self.buf.push_str("direction: right\n\n");
}

fn alloc_legend(&mut self, lines: &[String]) {
self.buf.push_str("ALLOCS: {\n");
self.buf.push_str(" style.fill: \"#ffffcc\"\n");
self.buf.push_str(" style.stroke: \"#999999\"\n");

let text = lines
.iter()
.map(|s| escape_d2(&ctx.render_stmt(s)))
.collect();
let term_str = escape_d2(&ctx.render_terminator(&block.terminator));
.map(|l| escape_d2(l))
.collect::<Vec<_>>()
.join("\\n");

let mut label = format!("bb{}:", idx);
for stmt in &stmts {
label.push_str(&format!("\\n{}", stmt));
}
label.push_str(&format!("\\n---\\n{}", term_str));
self.buf.push_str(&format!(" label: \"{}\"\n", text));
self.buf.push_str("}\n\n");
}

fn type_legend(&mut self, _lines: &[String]) {}

out.push_str(&format!(" bb{}: \"{}\"\n", idx, label));
fn external_function(&mut self, id: &str, name: &str) {
self.buf
.push_str(&format!("{}: \"{}\"\n", id, escape_d2(name)));
}
}

fn render_d2_block_edges(body: &stable_mir::mir::Body, out: &mut String) {
for (idx, block) in body.blocks.iter().enumerate() {
for target in terminator_targets(&block.terminator) {
out.push_str(&format!(" bb{} -> bb{}\n", idx, target));
fn render_function(&mut self, func: &RenderedFunction) {
self.buf.push_str(&format!("{}: {{\n", func.id));
self.buf
.push_str(&format!(" label: \"{}\"\n", escape_d2(&func.display_name)));
self.buf.push_str(" style.fill: \"#e0e0ff\"\n");

for block in &func.blocks {
let mut label = format!("bb{}:", block.idx);

for stmt in &block.stmts {
label.push_str(&format!("\\n{}", escape_d2(stmt)));
}

label.push_str(&format!("\\n---\\n{}", escape_d2(&block.terminator)));

self.buf
.push_str(&format!(" bb{}: \"{}\"\n", block.idx, label));
}
}
}

fn render_d2_call_edges(
fn_id: &str,
body: &stable_mir::mir::Body,
ctx: &GraphContext,
out: &mut String,
) {
for (idx, block) in body.blocks.iter().enumerate() {
let TerminatorKind::Call { func, .. } = &block.terminator.kind else {
continue;
};
let Some(callee_name) = ctx.resolve_call_target(func) else {
continue;
};
if !is_unqualified(&callee_name) {
continue;
for block in &func.blocks {
for (target, _) in &block.cfg_edges {
self.buf
.push_str(&format!(" bb{} -> bb{}\n", block.idx, target));
}
}

let target_id = short_name(&callee_name);
out.push_str(&format!("{}: \"{}\"\n", target_id, escape_d2(&callee_name)));
out.push_str(&format!("{}.style.fill: \"#ffe0e0\"\n", target_id));
out.push_str(&format!("{}.bb{} -> {}: call\n", fn_id, idx, target_id));
self.buf.push_str("}\n\n");

for edge in &func.call_edges {
let callee_id = short_name(&edge.callee_name);
self.buf.push_str(&format!(
"{}: \"{}\"\n",
callee_id,
escape_d2(&edge.callee_name)
));

self.buf
.push_str(&format!("{}.style.fill: \"#ffe0e0\"\n", callee_id));

self.buf.push_str(&format!(
"{}.bb{} -> {}: call\n",
func.id, edge.block_idx, callee_id
));
}
}
}

fn render_d2_asm(asm: &str, out: &mut String) {
let asm_id = short_name(asm);
let asm_text = escape_d2(&asm.lines().collect::<String>());
out.push_str(&format!("{}: \"{}\" {{\n", asm_id, asm_text));
out.push_str(" style.fill: \"#ffe0ff\"\n");
out.push_str("}\n\n");
fn static_item(&mut self, id: &str, name: &str) {
self.buf
.push_str(&format!("{}: \"{}\" {{\n", id, escape_d2(name)));
self.buf.push_str(" style.fill: \"#e0ffe0\"\n");
self.buf.push_str("}\n\n");
}

fn asm_item(&mut self, id: &str, content: &str) {
let text = escape_d2(&content.lines().collect::<String>());

self.buf.push_str(&format!("{}: \"{}\" {{\n", id, text));
self.buf.push_str(" style.fill: \"#ffe0ff\"\n");
self.buf.push_str("}\n\n");
}

fn finish(self) -> String {
self.buf
}
}

fn render_d2_static(name: &str, out: &mut String) {
let static_id = short_name(name);
out.push_str(&format!("{}: \"{}\" {{\n", static_id, escape_d2(name)));
out.push_str(" style.fill: \"#e0ffe0\"\n");
out.push_str("}\n\n");
// =============================================================================
// Public entry point
// =============================================================================

impl SmirJson {
/// Convert the MIR to D2 using GraphBuilder traversal
pub fn to_d2_file(&self) -> String {
render_graph(self, D2Builder::new())
}
}
Loading
Loading