From 1f0a4aa7905395379df0827e0aab59fa23580352 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Tue, 24 Feb 2026 09:36:00 +0530 Subject: [PATCH 01/17] mk_graph: introduce GraphBuilder trait --- src/mk_graph/traverse.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/mk_graph/traverse.rs diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs new file mode 100644 index 00000000..ce2ba2c8 --- /dev/null +++ b/src/mk_graph/traverse.rs @@ -0,0 +1,31 @@ +//! Generic MIR graph traversal. +//! +//! This module owns the traversal order and graph semantics. + +/// Format agnostic graph sink. +/// Implemented by all renderers. +pub trait GraphBuilder { + type Output; + + fn begin_graph(&mut self, name: &str); + + fn alloc_legend(&mut self, lines: &[String]); + + fn type_legend(&mut self, lines: &[String]); + + fn begin_function(&mut self, id: &str, label: &str, is_local: bool); + + fn block(&mut self, fn_id: &str, idx: usize, stmts: &[String], terminator: &str); + + fn block_edge(&mut self, fn_id: &str, from: usize, to: usize, label: Option<&str>); + + fn call_edge(&mut self, fn_id: &str, block: usize, callee_id: &str, callee_name: &str); + + fn end_function(&mut self, id: &str); + + fn static_item(&mut self, id: &str, name: &str); + + fn asm_item(&mut self, id: &str, content: &str); + + fn finish(self) -> Self::Output; +} From bb42c148af13a316a71c9018cb7e7be47171ad48 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Tue, 24 Feb 2026 11:22:28 +0530 Subject: [PATCH 02/17] d2: extract item traversal helper --- src/mk_graph/output/d2.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index c3ccc491..895bbeca 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -20,19 +20,7 @@ impl SmirJson { 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); - } - } - } + render_d2_items(&self.items, &ctx, &mut output); output } @@ -42,6 +30,22 @@ impl SmirJson { // D2 Rendering Helpers // ============================================================================= +fn render_d2_items(items: &[crate::printer::Item], ctx: &GraphContext, out: &mut String) { + for item in items { + match &item.mono_item_kind { + MonoItemKind::MonoItemFn { name, body, .. } => { + render_d2_function(name, body.as_ref(), ctx, out); + } + MonoItemKind::MonoItemGlobalAsm { asm } => { + render_d2_asm(asm, out); + } + MonoItemKind::MonoItemStatic { name, .. } => { + render_d2_static(name, out); + } + } + } +} + fn render_d2_allocs_legend(ctx: &GraphContext, out: &mut String) { let legend_lines = ctx.allocs_legend_lines(); From 6abfb4a9c1f63df5db33caab88df9e8e6de15286 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Tue, 24 Feb 2026 11:30:53 +0530 Subject: [PATCH 03/17] d2: introduce D2Builder struct --- src/mk_graph/output/d2.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index 895bbeca..6f3b9e49 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -11,7 +11,25 @@ use crate::mk_graph::util::{ escape_d2, is_unqualified, name_lines, short_name, terminator_targets, }; -impl SmirJson { +// ============================================================================= +// D2 Builder +// ============================================================================= + +struct D2Builder { + buf: String, +} + +impl D2Builder { + fn new() -> Self { + Self { buf: String::new() } + } +} + +// ============================================================================= +// Public entry point +// ============================================================================= + +impl SmirJson<'_> { /// Convert the MIR to D2 diagram format pub fn to_d2_file(self) -> String { let ctx = GraphContext::from_smir(&self); From 52823801f1493545ab87c0d15d24d692d1df4678 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Tue, 24 Feb 2026 12:08:11 +0530 Subject: [PATCH 04/17] d2: implement GraphBuilder for D2Builder --- src/mk_graph/output/d2.rs | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index 6f3b9e49..5e3795fb 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -11,6 +11,8 @@ use crate::mk_graph::util::{ escape_d2, is_unqualified, name_lines, short_name, terminator_targets, }; +use crate::mk_graph::traverse::GraphBuilder; + // ============================================================================= // D2 Builder // ============================================================================= @@ -25,6 +27,78 @@ impl D2Builder { } } +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 legend_text = lines + .iter() + .map(|s| escape_d2(s)) + .collect::>() + .join("\\n"); + self.buf.push_str(&format!(" label: \"{}\"\n", legend_text)); + self.buf.push_str("}\n\n"); + } + + fn type_legend(&mut self, _: &[String]) {} + + fn begin_function(&mut self, id: &str, label: &str, _is_local: bool) { + self.buf.push_str(&format!("{}: {{\n", id)); + self.buf.push_str(&format!(" label: \"{}\"\n", label)); + self.buf.push_str(" style.fill: \"#e0e0ff\"\n"); + } + + fn block( &mut self, _fn_id: &str, idx: usize, stmts: &[String], terminator: &str) { + let mut label = format!("bb{}:", idx); + for stmt in stmts { + label.push_str(&format!("\\n{}", stmt)); + } + label.push_str(&format!("\\n---\\n{}", terminator)); + + self.buf.push_str(&format!(" bb{}: \"{}\"\n", idx, label)); + } + + fn block_edge(&mut self, _fn_id: &str, from: usize, to: usize, _label: Option<&str>) { + self.buf.push_str(&format!(" bb{} -> bb{}\n", from, to)); + } + + fn call_edge(&mut self, fn_id: &str, block: usize, callee_id: &str, callee_name: &str) { + self.buf.push_str(&format!("{}: \"{}\"\n", callee_id, escape_d2(callee_name))); + self.buf.push_str(&format!("{}.style.fill: \"#ffe0e0\"\n", callee_id)); + self.buf.push_str(&format!("{}.bb{} -> {}: call\n", fn_id, block, callee_id + )); + } + + fn end_function(&mut self, _id: &str) { + self.buf.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 asm_text = escape_d2(&content.lines().collect::()); + self.buf.push_str(&format!("{}: \"{}\" {{\n", id, asm_text)); + self.buf.push_str(" style.fill: \"#ffe0ff\"\n"); + self.buf.push_str("}\n\n"); + } + + fn finish(self) -> String { + self.buf + } +} + // ============================================================================= // Public entry point // ============================================================================= From 543a515d0c67a0f30cb6565097ed4f4b4d930ec0 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Sun, 1 Mar 2026 10:13:14 +0530 Subject: [PATCH 05/17] mk_graph: add generic graph traversal --- src/mk_graph/mod.rs | 1 + src/mk_graph/traverse.rs | 97 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/src/mk_graph/mod.rs b/src/mk_graph/mod.rs index 480ee518..58fbe17d 100644 --- a/src/mk_graph/mod.rs +++ b/src/mk_graph/mod.rs @@ -15,6 +15,7 @@ pub mod context; pub mod index; pub mod output; pub mod util; +pub mod traverse; // Re-exports for convenience pub use context::GraphContext; diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs index ce2ba2c8..3f865405 100644 --- a/src/mk_graph/traverse.rs +++ b/src/mk_graph/traverse.rs @@ -1,6 +1,16 @@ //! Generic MIR graph traversal. //! //! This module owns the traversal order and graph semantics. +extern crate 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, +}; /// Format agnostic graph sink. /// Implemented by all renderers. @@ -29,3 +39,90 @@ pub trait GraphBuilder { fn finish(self) -> Self::Output; } + +pub fn render_graph( + smir: &SmirJson, + mut builder: B, +) -> B::Output { + let ctx = GraphContext::from_smir(smir); + + builder.begin_graph(&smir.name); + + builder.alloc_legend(&ctx.allocs_legend_lines()); + builder.type_legend(&ctx.types_legend_lines()); + + for item in &smir.items { + match &item.mono_item_kind { + MonoItemKind::MonoItemFn { name, body, .. } => { + render_function(&ctx, &mut builder, name, body.as_ref()); + } + MonoItemKind::MonoItemStatic { name, .. } => { + let id = short_name(name); + builder.static_item(&id, name); + } + MonoItemKind::MonoItemGlobalAsm { asm } => { + let id = short_name(asm); + builder.asm_item(&id, asm); + } + } + } + + builder.finish() +} + +fn render_function( + ctx: &GraphContext, + builder: &mut B, + name: &str, + body: Option<&stable_mir::mir::Body>, +) { + let fn_id = short_name(name); + let label = escape_d2(&name_lines(name)); + let is_local = true; // preserve current behavior + + builder.begin_function(&fn_id, &label, is_local); + + if let Some(body) = body { + // blocks + for (idx, block) in body.blocks.iter().enumerate() { + let stmts = block + .statements + .iter() + .map(|s| escape_d2(&ctx.render_stmt(s))) + .collect::>(); + + let term = escape_d2(&ctx.render_terminator(&block.terminator)); + + builder.block(&fn_id, idx, &stmts, &term); + } + + // CFG edges + for (idx, block) in body.blocks.iter().enumerate() { + for target in terminator_targets(&block.terminator) { + builder.block_edge(&fn_id, idx, target, None); + } + } + } + + builder.end_function(&fn_id); + + // Call edges (outside container) + if let Some(body) = body { + 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; + } + + let callee_id = short_name(&callee_name); + builder.call_edge(&fn_id, idx, &callee_id, &callee_name); + } + } +} From 7c36303b0f08e516d4b20dbe5ba9134f0a88a9a6 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Sun, 1 Mar 2026 10:39:47 +0530 Subject: [PATCH 06/17] d2: wire GraphBuilder traversal behind flag --- src/mk_graph/mod.rs | 8 +++++++- src/mk_graph/output/d2.rs | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/mk_graph/mod.rs b/src/mk_graph/mod.rs index 58fbe17d..e96028d7 100644 --- a/src/mk_graph/mod.rs +++ b/src/mk_graph/mod.rs @@ -46,7 +46,13 @@ 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 = if std::env::var("SMIR_D2_NEW").is_ok() { + smir.to_d2_file_new() + } else { + smir.to_d2_file() + }; match mir_output_path(tcx, "smir.d2") { OutputDest::Stdout => { diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index 5e3795fb..b5380dc4 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -12,6 +12,7 @@ use crate::mk_graph::util::{ }; use crate::mk_graph::traverse::GraphBuilder; +use crate::mk_graph::traverse::render_graph; // ============================================================================= // D2 Builder @@ -116,6 +117,11 @@ impl SmirJson<'_> { output } + + /// Convert the MIR to D2 using GraphBuilder traversal (experimental) + pub fn to_d2_file_new(&self) -> String { + render_graph(self, D2Builder::new()) + } } // ============================================================================= From 52e550eb7d351cc22483552ca02931785e825baf Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Sun, 1 Mar 2026 17:37:54 +0530 Subject: [PATCH 07/17] mk_graph: make traversal independent of D2 formatting --- src/mk_graph/output/d2.rs | 6 +++--- src/mk_graph/traverse.rs | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index b5380dc4..81c2d26b 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -52,16 +52,16 @@ impl GraphBuilder for D2Builder { fn begin_function(&mut self, id: &str, label: &str, _is_local: bool) { self.buf.push_str(&format!("{}: {{\n", id)); - self.buf.push_str(&format!(" label: \"{}\"\n", label)); + self.buf.push_str(&format!(" label: \"{}\"\n", escape_d2(label))); self.buf.push_str(" style.fill: \"#e0e0ff\"\n"); } fn block( &mut self, _fn_id: &str, idx: usize, stmts: &[String], terminator: &str) { let mut label = format!("bb{}:", idx); for stmt in stmts { - label.push_str(&format!("\\n{}", stmt)); + label.push_str(&format!("\\n{}", escape_d2(stmt))); } - label.push_str(&format!("\\n---\\n{}", terminator)); + label.push_str(&format!("\\n---\\n{}", escape_d2(terminator))); self.buf.push_str(&format!(" bb{}: \"{}\"\n", idx, label)); } diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs index 3f865405..302088b4 100644 --- a/src/mk_graph/traverse.rs +++ b/src/mk_graph/traverse.rs @@ -9,7 +9,7 @@ use crate::MonoItemKind; use crate::mk_graph::context::GraphContext; use crate::mk_graph::util::{ - escape_d2, is_unqualified, name_lines, short_name, terminator_targets, + is_unqualified, name_lines, short_name, terminator_targets, }; /// Format agnostic graph sink. @@ -77,8 +77,8 @@ fn render_function( body: Option<&stable_mir::mir::Body>, ) { let fn_id = short_name(name); - let label = escape_d2(&name_lines(name)); - let is_local = true; // preserve current behavior + let label = name_lines(name); + let is_local = true; builder.begin_function(&fn_id, &label, is_local); @@ -88,10 +88,10 @@ fn render_function( let stmts = block .statements .iter() - .map(|s| escape_d2(&ctx.render_stmt(s))) + .map(|s| ctx.render_stmt(s)) .collect::>(); - let term = escape_d2(&ctx.render_terminator(&block.terminator)); + let term = ctx.render_terminator(&block.terminator); builder.block(&fn_id, idx, &stmts, &term); } From 245d129652df37e47a0112bb93159d60ac73fb63 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Sun, 1 Mar 2026 17:59:04 +0530 Subject: [PATCH 08/17] mk_graph: add comments clarifying traversal responsibilities --- src/mk_graph/traverse.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs index 302088b4..9bdd0ee9 100644 --- a/src/mk_graph/traverse.rs +++ b/src/mk_graph/traverse.rs @@ -40,6 +40,8 @@ pub trait GraphBuilder { fn finish(self) -> Self::Output; } +/// Format-agnostic MIR graph traversal. +/// Owns traversal order and graph semantics, delegates rendering to `GraphBuilder`. pub fn render_graph( smir: &SmirJson, mut builder: B, @@ -70,6 +72,8 @@ pub fn render_graph( builder.finish() } +/// Emit graph events for a single function body. +/// Traverses blocks, CFG edges, and call edges without renderer-specific logic. fn render_function( ctx: &GraphContext, builder: &mut B, From 286bb97b6f8f58503ad8c017c223904bdeec8aa0 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Mon, 2 Mar 2026 14:47:58 +0530 Subject: [PATCH 09/17] mk_graph: extend GraphBuilder to pass statements, terminators, and call args --- src/mk_graph/mod.rs | 2 +- src/mk_graph/output/d2.rs | 54 ++++++++++++++++++++++++++------------- src/mk_graph/traverse.rs | 37 ++++++++++++--------------- 3 files changed, 53 insertions(+), 40 deletions(-) diff --git a/src/mk_graph/mod.rs b/src/mk_graph/mod.rs index e96028d7..17c7cfdc 100644 --- a/src/mk_graph/mod.rs +++ b/src/mk_graph/mod.rs @@ -14,8 +14,8 @@ use crate::printer::collect_smir; pub mod context; pub mod index; pub mod output; -pub mod util; pub mod traverse; +pub mod util; // Re-exports for convenience pub use context::GraphContext; diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index 81c2d26b..c19cbccf 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -1,7 +1,7 @@ //! D2 diagram format output for MIR graphs. use crate::compat::stable_mir; -use stable_mir::mir::TerminatorKind; +use stable_mir::mir::{Operand, Statement, Terminator, TerminatorKind}; use crate::printer::SmirJson; use crate::MonoItemKind; @@ -11,24 +11,28 @@ use crate::mk_graph::util::{ escape_d2, is_unqualified, name_lines, short_name, terminator_targets, }; -use crate::mk_graph::traverse::GraphBuilder; use crate::mk_graph::traverse::render_graph; +use crate::mk_graph::traverse::GraphBuilder; // ============================================================================= // D2 Builder // ============================================================================= -struct D2Builder { +pub struct D2Builder<'a> { + ctx: &'a GraphContext, buf: String, } -impl D2Builder { - fn new() -> Self { - Self { buf: String::new() } +impl<'a> D2Builder<'a> { + pub fn new(ctx: &'a GraphContext) -> Self { + Self { + ctx, + buf: String::new(), + } } } -impl GraphBuilder for D2Builder { +impl<'a> GraphBuilder for D2Builder<'a> { type Output = String; fn begin_graph(&mut self, _name: &str) { @@ -44,7 +48,8 @@ impl GraphBuilder for D2Builder { .map(|s| escape_d2(s)) .collect::>() .join("\\n"); - self.buf.push_str(&format!(" label: \"{}\"\n", legend_text)); + self.buf + .push_str(&format!(" label: \"{}\"\n", legend_text)); self.buf.push_str("}\n\n"); } @@ -52,16 +57,19 @@ impl GraphBuilder for D2Builder { fn begin_function(&mut self, id: &str, label: &str, _is_local: bool) { self.buf.push_str(&format!("{}: {{\n", id)); - self.buf.push_str(&format!(" label: \"{}\"\n", escape_d2(label))); + self.buf + .push_str(&format!(" label: \"{}\"\n", escape_d2(label))); self.buf.push_str(" style.fill: \"#e0e0ff\"\n"); } - fn block( &mut self, _fn_id: &str, idx: usize, stmts: &[String], terminator: &str) { + fn block(&mut self, _fn_id: &str, idx: usize, stmts: &[Statement], terminator: &Terminator) { let mut label = format!("bb{}:", idx); for stmt in stmts { - label.push_str(&format!("\\n{}", escape_d2(stmt))); + let s = self.ctx.render_stmt(stmt); + label.push_str(&format!("\\n{}", escape_d2(&s))); } - label.push_str(&format!("\\n---\\n{}", escape_d2(terminator))); + let term_str = self.ctx.render_terminator(terminator); + label.push_str(&format!("\\n---\\n{}", escape_d2(&term_str))); self.buf.push_str(&format!(" bb{}: \"{}\"\n", idx, label)); } @@ -70,11 +78,20 @@ impl GraphBuilder for D2Builder { self.buf.push_str(&format!(" bb{} -> bb{}\n", from, to)); } - fn call_edge(&mut self, fn_id: &str, block: usize, callee_id: &str, callee_name: &str) { - self.buf.push_str(&format!("{}: \"{}\"\n", callee_id, escape_d2(callee_name))); - self.buf.push_str(&format!("{}.style.fill: \"#ffe0e0\"\n", callee_id)); - self.buf.push_str(&format!("{}.bb{} -> {}: call\n", fn_id, block, callee_id - )); + fn call_edge( + &mut self, + fn_id: &str, + block: usize, + callee_id: &str, + callee_name: &str, + _args: &[Operand], + ) { + self.buf + .push_str(&format!("{}: \"{}\"\n", callee_id, escape_d2(callee_name))); + self.buf + .push_str(&format!("{}.style.fill: \"#ffe0e0\"\n", callee_id)); + self.buf + .push_str(&format!("{}.bb{} -> {}: call\n", fn_id, block, callee_id)); } fn end_function(&mut self, _id: &str) { @@ -120,7 +137,8 @@ impl SmirJson<'_> { /// Convert the MIR to D2 using GraphBuilder traversal (experimental) pub fn to_d2_file_new(&self) -> String { - render_graph(self, D2Builder::new()) + let ctx = GraphContext::from_smir(self); + render_graph(self, D2Builder::new(&ctx)) } } diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs index 9bdd0ee9..162bb5da 100644 --- a/src/mk_graph/traverse.rs +++ b/src/mk_graph/traverse.rs @@ -2,15 +2,13 @@ //! //! This module owns the traversal order and graph semantics. extern crate stable_mir; -use stable_mir::mir::TerminatorKind; +use stable_mir::mir::{Operand, Statement, Terminator, TerminatorKind}; use crate::printer::SmirJson; use crate::MonoItemKind; use crate::mk_graph::context::GraphContext; -use crate::mk_graph::util::{ - is_unqualified, name_lines, short_name, terminator_targets, -}; +use crate::mk_graph::util::{is_unqualified, name_lines, short_name, terminator_targets}; /// Format agnostic graph sink. /// Implemented by all renderers. @@ -25,11 +23,18 @@ pub trait GraphBuilder { fn begin_function(&mut self, id: &str, label: &str, is_local: bool); - fn block(&mut self, fn_id: &str, idx: usize, stmts: &[String], terminator: &str); + fn block(&mut self, fn_id: &str, idx: usize, stmts: &[Statement], terminator: &Terminator); fn block_edge(&mut self, fn_id: &str, from: usize, to: usize, label: Option<&str>); - fn call_edge(&mut self, fn_id: &str, block: usize, callee_id: &str, callee_name: &str); + fn call_edge( + &mut self, + fn_id: &str, + block: usize, + callee_id: &str, + callee_name: &str, + args: &[Operand], + ); fn end_function(&mut self, id: &str); @@ -42,10 +47,7 @@ pub trait GraphBuilder { /// Format-agnostic MIR graph traversal. /// Owns traversal order and graph semantics, delegates rendering to `GraphBuilder`. -pub fn render_graph( - smir: &SmirJson, - mut builder: B, -) -> B::Output { +pub fn render_graph(smir: &SmirJson, mut builder: B) -> B::Output { let ctx = GraphContext::from_smir(smir); builder.begin_graph(&smir.name); @@ -89,15 +91,7 @@ fn render_function( if let Some(body) = body { // blocks for (idx, block) in body.blocks.iter().enumerate() { - let stmts = block - .statements - .iter() - .map(|s| ctx.render_stmt(s)) - .collect::>(); - - let term = ctx.render_terminator(&block.terminator); - - builder.block(&fn_id, idx, &stmts, &term); + builder.block(&fn_id, idx, &block.statements, &block.terminator); } // CFG edges @@ -113,7 +107,7 @@ fn render_function( // Call edges (outside container) if let Some(body) = body { for (idx, block) in body.blocks.iter().enumerate() { - let TerminatorKind::Call { func, .. } = &block.terminator.kind else { + let TerminatorKind::Call { func, args, .. } = &block.terminator.kind else { continue; }; @@ -126,7 +120,8 @@ fn render_function( } let callee_id = short_name(&callee_name); - builder.call_edge(&fn_id, idx, &callee_id, &callee_name); + + builder.call_edge(&fn_id, idx, &callee_id, &callee_name, args); } } } From cc7b44c93f92275043a8e0634dc158e211605447 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Mon, 2 Mar 2026 15:08:35 +0530 Subject: [PATCH 10/17] d2: elide explicit lifetime in GraphBuilder impl --- src/mk_graph/output/d2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index c19cbccf..4190861e 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -32,7 +32,7 @@ impl<'a> D2Builder<'a> { } } -impl<'a> GraphBuilder for D2Builder<'a> { +impl GraphBuilder for D2Builder<'_> { type Output = String; fn begin_graph(&mut self, _name: &str) { From b737cd50a43d5ce3b5f7aad2c96a7e20cb0eca80 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Thu, 5 Mar 2026 07:23:21 +0530 Subject: [PATCH 11/17] mk_graph: introduce RenderedFunction, RenderedBlock and CallEdge structures --- src/mk_graph/traverse.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs index 162bb5da..051aa103 100644 --- a/src/mk_graph/traverse.rs +++ b/src/mk_graph/traverse.rs @@ -10,6 +10,33 @@ use crate::MonoItemKind; use crate::mk_graph::context::GraphContext; use crate::mk_graph::util::{is_unqualified, name_lines, short_name, terminator_targets}; +/// A single call edge discovered during traversal. +pub struct CallEdge { + pub block_idx: usize, + pub callee_id: String, + pub callee_name: String, + pub rendered_args: String, +} + +/// A single basic block with pre-rendered content. +pub struct RenderedBlock<'a> { + pub idx: usize, + pub stmts: Vec, + pub terminator: String, + pub raw_terminator: &'a Terminator, + pub cfg_edges: Vec<(usize, Option)>, +} + +/// A fully rendered function ready for format-specific builders. +pub struct RenderedFunction<'a> { + pub id: String, + pub display_name: String, + pub is_local: bool, + pub locals: Vec<(usize, String)>, + pub blocks: Vec>, + pub call_edges: Vec, +} + /// Format agnostic graph sink. /// Implemented by all renderers. pub trait GraphBuilder { From 36eb8b62b028c12172ff7a82497c7517f03c7fe6 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Thu, 5 Mar 2026 10:56:55 +0530 Subject: [PATCH 12/17] mk_graph: refactor traversal and builders to use RenderedFunction model --- src/mk_graph/output/d2.rs | 121 +++++++++++++++++++++----------------- src/mk_graph/traverse.rs | 118 +++++++++++++++++++++---------------- 2 files changed, 134 insertions(+), 105 deletions(-) diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index 4190861e..e1b90144 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -1,7 +1,7 @@ //! D2 diagram format output for MIR graphs. use crate::compat::stable_mir; -use stable_mir::mir::{Operand, Statement, Terminator, TerminatorKind}; +use stable_mir::mir::{TerminatorKind}; use crate::printer::SmirJson; use crate::MonoItemKind; @@ -12,27 +12,23 @@ use crate::mk_graph::util::{ }; use crate::mk_graph::traverse::render_graph; -use crate::mk_graph::traverse::GraphBuilder; +use crate::mk_graph::traverse::{GraphBuilder, RenderedFunction}; // ============================================================================= // D2 Builder // ============================================================================= -pub struct D2Builder<'a> { - ctx: &'a GraphContext, +pub struct D2Builder { buf: String, } -impl<'a> D2Builder<'a> { - pub fn new(ctx: &'a GraphContext) -> Self { - Self { - ctx, - buf: String::new(), - } +impl D2Builder { + pub fn new() -> Self { + Self { buf: String::new() } } } -impl GraphBuilder for D2Builder<'_> { +impl GraphBuilder for D2Builder { type Output = String; fn begin_graph(&mut self, _name: &str) { @@ -40,65 +36,83 @@ impl GraphBuilder for D2Builder<'_> { } 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 legend_text = lines + + let text = lines .iter() - .map(|s| escape_d2(s)) + .map(|l| escape_d2(l)) .collect::>() .join("\\n"); - self.buf - .push_str(&format!(" label: \"{}\"\n", legend_text)); + + self.buf.push_str(&format!(" label: \"{}\"\n", text)); self.buf.push_str("}\n\n"); } - fn type_legend(&mut self, _: &[String]) {} + fn type_legend(&mut self, _lines: &[String]) {} + + fn external_function(&mut self, id: &str, name: &str) { - fn begin_function(&mut self, id: &str, label: &str, _is_local: bool) { - self.buf.push_str(&format!("{}: {{\n", id)); self.buf - .push_str(&format!(" label: \"{}\"\n", escape_d2(label))); - self.buf.push_str(" style.fill: \"#e0e0ff\"\n"); + .push_str(&format!("{}: \"{}\"\n", id, escape_d2(name))); } - fn block(&mut self, _fn_id: &str, idx: usize, stmts: &[Statement], terminator: &Terminator) { - let mut label = format!("bb{}:", idx); - for stmt in stmts { - let s = self.ctx.render_stmt(stmt); - label.push_str(&format!("\\n{}", escape_d2(&s))); - } - let term_str = self.ctx.render_terminator(terminator); - label.push_str(&format!("\\n---\\n{}", escape_d2(&term_str))); + fn render_function(&mut self, func: &RenderedFunction) { - self.buf.push_str(&format!(" bb{}: \"{}\"\n", idx, label)); - } + 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"); - fn block_edge(&mut self, _fn_id: &str, from: usize, to: usize, _label: Option<&str>) { - self.buf.push_str(&format!(" bb{} -> bb{}\n", from, to)); - } + for block in &func.blocks { - fn call_edge( - &mut self, - fn_id: &str, - block: usize, - callee_id: &str, - callee_name: &str, - _args: &[Operand], - ) { - self.buf - .push_str(&format!("{}: \"{}\"\n", callee_id, escape_d2(callee_name))); - self.buf - .push_str(&format!("{}.style.fill: \"#ffe0e0\"\n", callee_id)); - self.buf - .push_str(&format!("{}.bb{} -> {}: call\n", fn_id, block, callee_id)); - } + 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)); + } + + for block in &func.blocks { + for (target, _) in &block.cfg_edges { + self.buf + .push_str(&format!(" bb{} -> bb{}\n", block.idx, target)); + } + } - fn end_function(&mut self, _id: &str) { self.buf.push_str("}\n\n"); + + for edge in &func.call_edges { + + self.buf.push_str(&format!( + "{}: \"{}\"\n", + edge.callee_id, + escape_d2(&edge.callee_name) + )); + + self.buf.push_str(&format!( + "{}.style.fill: \"#ffe0e0\"\n", + edge.callee_id + )); + + self.buf.push_str(&format!( + "{}.bb{} -> {}: call\n", + func.id, + edge.block_idx, + edge.callee_id + )); + } } 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"); @@ -106,8 +120,10 @@ impl GraphBuilder for D2Builder<'_> { } fn asm_item(&mut self, id: &str, content: &str) { - let asm_text = escape_d2(&content.lines().collect::()); - self.buf.push_str(&format!("{}: \"{}\" {{\n", id, asm_text)); + + let text = escape_d2(&content.lines().collect::()); + + self.buf.push_str(&format!("{}: \"{}\" {{\n", id, text)); self.buf.push_str(" style.fill: \"#ffe0ff\"\n"); self.buf.push_str("}\n\n"); } @@ -137,8 +153,7 @@ impl SmirJson<'_> { /// Convert the MIR to D2 using GraphBuilder traversal (experimental) pub fn to_d2_file_new(&self) -> String { - let ctx = GraphContext::from_smir(self); - render_graph(self, D2Builder::new(&ctx)) + render_graph(self, D2Builder::new()) } } diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs index 051aa103..40fdf59f 100644 --- a/src/mk_graph/traverse.rs +++ b/src/mk_graph/traverse.rs @@ -2,7 +2,7 @@ //! //! This module owns the traversal order and graph semantics. extern crate stable_mir; -use stable_mir::mir::{Operand, Statement, Terminator, TerminatorKind}; +use stable_mir::mir::{Body, Terminator, TerminatorKind}; use crate::printer::SmirJson; use crate::MonoItemKind; @@ -48,22 +48,9 @@ pub trait GraphBuilder { fn type_legend(&mut self, lines: &[String]); - fn begin_function(&mut self, id: &str, label: &str, is_local: bool); + fn external_function(&mut self, id: &str, name: &str); - fn block(&mut self, fn_id: &str, idx: usize, stmts: &[Statement], terminator: &Terminator); - - fn block_edge(&mut self, fn_id: &str, from: usize, to: usize, label: Option<&str>); - - fn call_edge( - &mut self, - fn_id: &str, - block: usize, - callee_id: &str, - callee_name: &str, - args: &[Operand], - ); - - fn end_function(&mut self, id: &str); + fn render_function(&mut self, func: &RenderedFunction); fn static_item(&mut self, id: &str, name: &str); @@ -85,7 +72,8 @@ pub fn render_graph(smir: &SmirJson, mut builder: B) -> B::Outp for item in &smir.items { match &item.mono_item_kind { MonoItemKind::MonoItemFn { name, body, .. } => { - render_function(&ctx, &mut builder, name, body.as_ref()); + let func = render_function(&ctx, name, body.as_ref()); + builder.render_function(&func); } MonoItemKind::MonoItemStatic { name, .. } => { let id = short_name(name); @@ -103,52 +91,78 @@ pub fn render_graph(smir: &SmirJson, mut builder: B) -> B::Outp /// Emit graph events for a single function body. /// Traverses blocks, CFG edges, and call edges without renderer-specific logic. -fn render_function( +fn render_function<'a>( ctx: &GraphContext, - builder: &mut B, name: &str, - body: Option<&stable_mir::mir::Body>, -) { - let fn_id = short_name(name); - let label = name_lines(name); - let is_local = true; + body: Option<&'a Body>, +) -> RenderedFunction<'a> { + let id = short_name(name); + let display_name = name_lines(name); + let is_local = body.is_some(); - builder.begin_function(&fn_id, &label, is_local); + let mut blocks = Vec::new(); + let mut call_edges = Vec::new(); + let mut locals = Vec::new(); if let Some(body) = body { - // blocks - for (idx, block) in body.blocks.iter().enumerate() { - builder.block(&fn_id, idx, &block.statements, &block.terminator); - } - // CFG edges - for (idx, block) in body.blocks.iter().enumerate() { - for target in terminator_targets(&block.terminator) { - builder.block_edge(&fn_id, idx, target, None); - } + for (idx, decl) in body.local_decls() { + locals.push((idx, ctx.render_type_with_layout(decl.ty))); } - } - builder.end_function(&fn_id); - - // Call edges (outside container) - if let Some(body) = body { for (idx, block) in body.blocks.iter().enumerate() { - let TerminatorKind::Call { func, args, .. } = &block.terminator.kind else { - continue; - }; - let Some(callee_name) = ctx.resolve_call_target(func) else { - continue; - }; - - if !is_unqualified(&callee_name) { - continue; + let stmts = block + .statements + .iter() + .map(|s| ctx.render_stmt(s)) + .collect(); + + let terminator = ctx.render_terminator(&block.terminator); + + let cfg_edges = terminator_targets(&block.terminator) + .into_iter() + .map(|t| (t, None)) + .collect(); + + blocks.push(RenderedBlock { + idx, + stmts, + terminator, + raw_terminator: &block.terminator, + cfg_edges, + }); + + if let TerminatorKind::Call { func, args, .. } = &block.terminator.kind { + + if let Some(callee) = ctx.resolve_call_target(func) { + + if is_unqualified(&callee) { + + let rendered_args = args + .iter() + .map(|a| ctx.render_operand(a)) + .collect::>() + .join(", "); + + call_edges.push(CallEdge { + block_idx: idx, + callee_id: short_name(&callee), + callee_name: callee, + rendered_args, + }); + } + } } - - let callee_id = short_name(&callee_name); - - builder.call_edge(&fn_id, idx, &callee_id, &callee_name, args); } } + + RenderedFunction { + id, + display_name, + is_local, + locals, + blocks, + call_edges, + } } From 2c3d19a2446455bb6cd17c120d63b58e4ef67504 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Thu, 5 Mar 2026 12:03:33 +0530 Subject: [PATCH 13/17] mk_graph: add body hash to function IDs to avoid collisions --- src/mk_graph/traverse.rs | 8 ++++++-- src/mk_graph/util.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs index 40fdf59f..4dec2a16 100644 --- a/src/mk_graph/traverse.rs +++ b/src/mk_graph/traverse.rs @@ -8,7 +8,7 @@ use crate::printer::SmirJson; use crate::MonoItemKind; use crate::mk_graph::context::GraphContext; -use crate::mk_graph::util::{is_unqualified, name_lines, short_name, terminator_targets}; +use crate::mk_graph::util::{is_unqualified, name_lines, short_name, terminator_targets, hash_body}; /// A single call edge discovered during traversal. pub struct CallEdge { @@ -96,7 +96,11 @@ fn render_function<'a>( name: &str, body: Option<&'a Body>, ) -> RenderedFunction<'a> { - let id = short_name(name); + let id = match body { + Some(b) => format!("fn_{}_{}", short_name(name), hash_body(b)), + None => format!("fn_{}_no_body", short_name(name)), + }; + let display_name = name_lines(name); let is_local = body.is_some(); diff --git a/src/mk_graph/util.rs b/src/mk_graph/util.rs index ac21cbb1..db7a8efa 100644 --- a/src/mk_graph/util.rs +++ b/src/mk_graph/util.rs @@ -5,7 +5,7 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use crate::compat::stable_mir; use stable_mir::mir::{ AggregateKind, BorrowKind, ConstOperand, Mutability, NonDivergingIntrinsic, NullOp, Operand, - Place, ProjectionElem, Rvalue, Terminator, TerminatorKind, UnwindAction, + Place, ProjectionElem, Rvalue, Terminator, TerminatorKind, UnwindAction, Body, }; use stable_mir::ty::{IndexedVal, RigidTy}; @@ -280,3 +280,29 @@ pub fn terminator_targets(term: &Terminator) -> Vec { } } } + +/// Generate a consistent short hash for a MIR body. +/// Used to avoid fn_id collisions between monomorphizations. +pub fn hash_body(body: &Body) -> u64 { + let mut h = DefaultHasher::new(); + + // Hash number of blocks + body.blocks.len().hash(&mut h); + + for (idx, block) in body.blocks.iter().enumerate() { + idx.hash(&mut h); + + // Hash terminator kind + std::mem::discriminant(&block.terminator.kind).hash(&mut h); + + // Hash control-flow edges + for target in terminator_targets(&block.terminator) { + target.hash(&mut h); + } + + // Statement count for entropy + block.statements.len().hash(&mut h); + } + + h.finish() +} From 9bfbd90cb00b13d1f3ac8c79a9dd1bc312846126 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Thu, 5 Mar 2026 12:11:55 +0530 Subject: [PATCH 14/17] d2: remove SMIR_D2_NEW flag and use new traversal by default --- src/mk_graph/mod.rs | 6 +- src/mk_graph/output/d2.rs | 152 +------------------------------------- 2 files changed, 4 insertions(+), 154 deletions(-) diff --git a/src/mk_graph/mod.rs b/src/mk_graph/mod.rs index 17c7cfdc..e93c4d00 100644 --- a/src/mk_graph/mod.rs +++ b/src/mk_graph/mod.rs @@ -48,11 +48,7 @@ pub fn emit_dotfile(tcx: TyCtxt<'_>) { pub fn emit_d2file(tcx: TyCtxt<'_>) { let smir = collect_smir(tcx); - let smir_d2 = if std::env::var("SMIR_D2_NEW").is_ok() { - smir.to_d2_file_new() - } else { - smir.to_d2_file() - }; + let smir_d2 = smir.to_d2_file(); match mir_output_path(tcx, "smir.d2") { OutputDest::Stdout => { diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index e1b90144..f3ab22a7 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -1,15 +1,10 @@ //! 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; use crate::mk_graph::traverse::render_graph; use crate::mk_graph::traverse::{GraphBuilder, RenderedFunction}; @@ -138,149 +133,8 @@ impl GraphBuilder for D2Builder { // ============================================================================= 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); - - render_d2_items(&self.items, &ctx, &mut output); - - output - } - - /// Convert the MIR to D2 using GraphBuilder traversal (experimental) - pub fn to_d2_file_new(&self) -> String { + /// Convert the MIR to D2 using GraphBuilder traversal + pub fn to_d2_file(&self) -> String { render_graph(self, D2Builder::new()) } } - -// ============================================================================= -// D2 Rendering Helpers -// ============================================================================= - -fn render_d2_items(items: &[crate::printer::Item], ctx: &GraphContext, out: &mut String) { - for item in items { - match &item.mono_item_kind { - MonoItemKind::MonoItemFn { name, body, .. } => { - render_d2_function(name, body.as_ref(), ctx, out); - } - MonoItemKind::MonoItemGlobalAsm { asm } => { - render_d2_asm(asm, out); - } - MonoItemKind::MonoItemStatic { name, .. } => { - render_d2_static(name, out); - } - } - } -} - -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::>() - .join("\\n"); - out.push_str(&format!(" label: \"{}\"\n", legend_text)); - out.push_str("}\n\n"); -} - -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); - } - - 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); - } -} - -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 = block - .statements - .iter() - .map(|s| escape_d2(&ctx.render_stmt(s))) - .collect(); - let term_str = escape_d2(&ctx.render_terminator(&block.terminator)); - - let mut label = format!("bb{}:", idx); - for stmt in &stmts { - label.push_str(&format!("\\n{}", stmt)); - } - label.push_str(&format!("\\n---\\n{}", term_str)); - - out.push_str(&format!(" bb{}: \"{}\"\n", idx, label)); - } -} - -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_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; - } - - 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)); - } -} - -fn render_d2_asm(asm: &str, out: &mut String) { - let asm_id = short_name(asm); - let asm_text = escape_d2(&asm.lines().collect::()); - out.push_str(&format!("{}: \"{}\" {{\n", asm_id, asm_text)); - out.push_str(" style.fill: \"#ffe0ff\"\n"); - out.push_str("}\n\n"); -} - -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"); -} From ac185266c349c33cd186fe1550fa1e8dc4efb872 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Thu, 5 Mar 2026 12:23:50 +0530 Subject: [PATCH 15/17] mk_graph: expand documentation for traversal and rendering structures Add detailed Rustdoc comments for GraphBuilder, RenderedFunction, RenderedBlock, CallEdge, and traversal entry points. The documentation explains the separation between MIR traversal and format-specific rendering, and clarifies the responsibilities of each structure. This improves discoverability via `cargo doc` and makes the graph rendering architecture easier to understand for future contributors. --- src/mk_graph/traverse.rs | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs index 4dec2a16..ac564385 100644 --- a/src/mk_graph/traverse.rs +++ b/src/mk_graph/traverse.rs @@ -10,7 +10,10 @@ use crate::MonoItemKind; use crate::mk_graph::context::GraphContext; use crate::mk_graph::util::{is_unqualified, name_lines, short_name, terminator_targets, hash_body}; -/// A single call edge discovered during traversal. +/// Represents a call from a block to another function. +/// +/// The callee is resolved during traversal and arguments are already +/// rendered as a string. Builders may choose how to visualize this edge. pub struct CallEdge { pub block_idx: usize, pub callee_id: String, @@ -18,7 +21,14 @@ pub struct CallEdge { pub rendered_args: String, } -/// A single basic block with pre-rendered content. +/// A basic block with pre-rendered textual content and structural edges. +/// +/// `stmts` and `terminator` are pre-rendered strings produced using +/// `GraphContext`. Builders are free to format or escape them according +/// to their output format. +/// +/// `raw_terminator` is provided as an escape hatch for renderers that +/// need to inspect the underlying MIR structure. pub struct RenderedBlock<'a> { pub idx: usize, pub stmts: Vec, @@ -27,7 +37,12 @@ pub struct RenderedBlock<'a> { pub cfg_edges: Vec<(usize, Option)>, } -/// A fully rendered function ready for format-specific builders. +/// A fully analyzed MIR function ready for rendering. +/// +/// The traversal layer resolves call targets, renders statements and +/// terminators, and computes the control-flow edges. Builders receive +/// this structure and are responsible only for formatting it into a +/// specific graph representation. pub struct RenderedFunction<'a> { pub id: String, pub display_name: String, @@ -37,8 +52,16 @@ pub struct RenderedFunction<'a> { pub call_edges: Vec, } -/// Format agnostic graph sink. -/// Implemented by all renderers. +/// Trait implemented by graph renderers. +/// +/// The traversal layer walks the MIR graph and constructs a +/// `RenderedFunction` representation. Implementations of this trait +/// consume those structures and emit format-specific output such as +/// D2, DOT, or other diagram formats. +/// +/// The trait intentionally separates graph structure from formatting. +/// Traversal decides *what* the graph contains while the builder +/// decides *how* it is rendered. pub trait GraphBuilder { type Output; @@ -59,8 +82,11 @@ pub trait GraphBuilder { fn finish(self) -> Self::Output; } -/// Format-agnostic MIR graph traversal. -/// Owns traversal order and graph semantics, delegates rendering to `GraphBuilder`. +/// Traverse the SMIR representation and produce rendered graph data. +/// +/// This function performs MIR traversal, resolves call targets, and +/// constructs `RenderedFunction` structures which are then passed to +/// the provided `GraphBuilder`. pub fn render_graph(smir: &SmirJson, mut builder: B) -> B::Output { let ctx = GraphContext::from_smir(smir); From 95528493bdef59a1917559605bfce59e9bb42d2e Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Thu, 5 Mar 2026 12:55:52 +0530 Subject: [PATCH 16/17] d2: fix build issues after rebase and address clippy warnings Resolve compilation issues introduced after rebasing onto upstream master. Remove an unused import, update the SmirJson impl to match the new upstream definition without lifetime parameters, and implement Default for D2Builder to satisfy Clippy's new_without_default lint. Run cargo fmt to normalize formatting. --- src/mk_graph/output/d2.rs | 27 ++++++++++----------------- src/mk_graph/traverse.rs | 9 +++------ src/mk_graph/util.rs | 4 ++-- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index f3ab22a7..843a1223 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -1,7 +1,5 @@ //! D2 diagram format output for MIR graphs. -use crate::compat::stable_mir; - use crate::printer::SmirJson; use crate::mk_graph::util::escape_d2; @@ -23,6 +21,12 @@ impl D2Builder { } } +impl Default for D2Builder { + fn default() -> Self { + Self::new() + } +} + impl GraphBuilder for D2Builder { type Output = String; @@ -31,7 +35,6 @@ impl GraphBuilder for D2Builder { } 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"); @@ -49,20 +52,17 @@ impl GraphBuilder for D2Builder { fn type_legend(&mut self, _lines: &[String]) {} fn external_function(&mut self, id: &str, name: &str) { - self.buf .push_str(&format!("{}: \"{}\"\n", id, escape_d2(name))); } 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 { @@ -85,29 +85,23 @@ impl GraphBuilder for D2Builder { self.buf.push_str("}\n\n"); for edge in &func.call_edges { - self.buf.push_str(&format!( "{}: \"{}\"\n", edge.callee_id, escape_d2(&edge.callee_name) )); - self.buf.push_str(&format!( - "{}.style.fill: \"#ffe0e0\"\n", - edge.callee_id - )); + self.buf + .push_str(&format!("{}.style.fill: \"#ffe0e0\"\n", edge.callee_id)); self.buf.push_str(&format!( "{}.bb{} -> {}: call\n", - func.id, - edge.block_idx, - edge.callee_id + func.id, edge.block_idx, edge.callee_id )); } } 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"); @@ -115,7 +109,6 @@ impl GraphBuilder for D2Builder { } fn asm_item(&mut self, id: &str, content: &str) { - let text = escape_d2(&content.lines().collect::()); self.buf.push_str(&format!("{}: \"{}\" {{\n", id, text)); @@ -132,7 +125,7 @@ impl GraphBuilder for D2Builder { // Public entry point // ============================================================================= -impl SmirJson<'_> { +impl SmirJson { /// Convert the MIR to D2 using GraphBuilder traversal pub fn to_d2_file(&self) -> String { render_graph(self, D2Builder::new()) diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs index ac564385..7067bd22 100644 --- a/src/mk_graph/traverse.rs +++ b/src/mk_graph/traverse.rs @@ -8,7 +8,9 @@ use crate::printer::SmirJson; use crate::MonoItemKind; use crate::mk_graph::context::GraphContext; -use crate::mk_graph::util::{is_unqualified, name_lines, short_name, terminator_targets, hash_body}; +use crate::mk_graph::util::{ + hash_body, is_unqualified, name_lines, short_name, terminator_targets, +}; /// Represents a call from a block to another function. /// @@ -135,13 +137,11 @@ fn render_function<'a>( let mut locals = Vec::new(); if let Some(body) = body { - for (idx, decl) in body.local_decls() { locals.push((idx, ctx.render_type_with_layout(decl.ty))); } for (idx, block) in body.blocks.iter().enumerate() { - let stmts = block .statements .iter() @@ -164,11 +164,8 @@ fn render_function<'a>( }); if let TerminatorKind::Call { func, args, .. } = &block.terminator.kind { - if let Some(callee) = ctx.resolve_call_target(func) { - if is_unqualified(&callee) { - let rendered_args = args .iter() .map(|a| ctx.render_operand(a)) diff --git a/src/mk_graph/util.rs b/src/mk_graph/util.rs index db7a8efa..d7708c74 100644 --- a/src/mk_graph/util.rs +++ b/src/mk_graph/util.rs @@ -4,8 +4,8 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use crate::compat::stable_mir; use stable_mir::mir::{ - AggregateKind, BorrowKind, ConstOperand, Mutability, NonDivergingIntrinsic, NullOp, Operand, - Place, ProjectionElem, Rvalue, Terminator, TerminatorKind, UnwindAction, Body, + AggregateKind, Body, BorrowKind, ConstOperand, Mutability, NonDivergingIntrinsic, NullOp, + Operand, Place, ProjectionElem, Rvalue, Terminator, TerminatorKind, UnwindAction, }; use stable_mir::ty::{IndexedVal, RigidTy}; From d2f355bf52cf05c33f4fc458a18ec2e4d1fbe7c1 Mon Sep 17 00:00:00 2001 From: 0xh4ty <0xh4ty@proton.me> Date: Thu, 5 Mar 2026 13:10:20 +0530 Subject: [PATCH 17/17] mk_graph: remove is_local and add raw_stmts to RenderedBlock Remove the unused is_local field from RenderedFunction since the local/external distinction is already represented structurally in the GraphBuilder API. Add raw_stmts to RenderedBlock as an escape hatch for renderers that need access to the underlying MIR statements in addition to the pre-rendered strings. --- src/mk_graph/traverse.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/mk_graph/traverse.rs b/src/mk_graph/traverse.rs index 7067bd22..1d9bd9b1 100644 --- a/src/mk_graph/traverse.rs +++ b/src/mk_graph/traverse.rs @@ -2,7 +2,7 @@ //! //! This module owns the traversal order and graph semantics. extern crate stable_mir; -use stable_mir::mir::{Body, Terminator, TerminatorKind}; +use stable_mir::mir::{Body, Statement, Terminator, TerminatorKind}; use crate::printer::SmirJson; use crate::MonoItemKind; @@ -29,11 +29,12 @@ pub struct CallEdge { /// `GraphContext`. Builders are free to format or escape them according /// to their output format. /// -/// `raw_terminator` is provided as an escape hatch for renderers that -/// need to inspect the underlying MIR structure. +/// `raw_stmts` and `raw_terminator` are escape hatches for renderers +/// that need to inspect the underlying MIR structure. pub struct RenderedBlock<'a> { pub idx: usize, pub stmts: Vec, + pub raw_stmts: &'a [Statement], pub terminator: String, pub raw_terminator: &'a Terminator, pub cfg_edges: Vec<(usize, Option)>, @@ -48,7 +49,6 @@ pub struct RenderedBlock<'a> { pub struct RenderedFunction<'a> { pub id: String, pub display_name: String, - pub is_local: bool, pub locals: Vec<(usize, String)>, pub blocks: Vec>, pub call_edges: Vec, @@ -130,7 +130,6 @@ fn render_function<'a>( }; let display_name = name_lines(name); - let is_local = body.is_some(); let mut blocks = Vec::new(); let mut call_edges = Vec::new(); @@ -158,6 +157,7 @@ fn render_function<'a>( blocks.push(RenderedBlock { idx, stmts, + raw_stmts: &block.statements, terminator, raw_terminator: &block.terminator, cfg_edges, @@ -187,7 +187,6 @@ fn render_function<'a>( RenderedFunction { id, display_name, - is_local, locals, blocks, call_edges,