From a2a8f7d7705cd8e2abb8387521124356b8afb7b1 Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Thu, 23 Jan 2025 15:03:50 +1100 Subject: [PATCH 01/17] behaviour-preserving refactoring to introduce SmirJson struct (keeping debug info) --- src/printer.rs | 73 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index 3207266..b9b5b93 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -19,8 +19,8 @@ use stable_mir::{ CrateItem, CrateDef, ItemKind, - mir::{Body,LocalDecl,Terminator,TerminatorKind,Rvalue,visit::MirVisitor}, - ty::{Allocation,ForeignItemKind}, + mir::{Body,LocalDecl,Terminator,TerminatorKind,Rvalue,alloc::AllocId,visit::MirVisitor}, + ty::{Allocation,ConstDef,ForeignItemKind}, mir::mono::{MonoItem,Instance,InstanceKind} }; use serde::{Serialize, Serializer}; @@ -716,6 +716,25 @@ fn collect_items(tcx: TyCtxt<'_>) -> HashMap { }).collect::>() } +/// the serialised data structure as a whole +#[derive(Serialize)] +pub struct SmirJson<'t> { + name: String, + crate_id: u64, + allocs: Vec<(AllocId,AllocInfo)>, + functions: Vec<(LinkMapKey<'t>, FnSymType)>, + uneval_consts: Vec<(ConstDef, String)>, + items: Vec, + debug: Option> +} + +#[derive(Serialize)] +struct SmirJsonDebugInfo<'t> { + fn_sources: Vec<(LinkMapKey<'t>,ItemSource)>, + types: TyMap, + foreign_modules: Vec<(String, Vec)> +} + // Serialization Entrypoint // ======================== @@ -738,28 +757,36 @@ fn emit_smir_internal(tcx: TyCtxt<'_>, writer: &mut dyn io::Write) { } } - let called_functions = calls_map.iter().map(|(k,(_,name))| (k,name)).collect::>(); - let allocs = visited_allocs.iter().collect::>(); + let calls_clone = calls_map.clone(); + let debug: Option = + if debug_enabled() { + let fn_sources = calls_clone.into_iter().map(|(k,(source,_))| (k,source)).collect::>(); + Some(SmirJsonDebugInfo { fn_sources, types: visited_tys, foreign_modules: get_foreign_module_details()}) + // write!(writer, "{{ \"fn_sources\": {}, \"types\": {}, \"foreign_modules\": {}}}", + // serde_json::to_string(&fn_sources).expect("serde_json functions failed"), + // serde_json::to_string(&visited_tys).expect("serde_json tys failed"), + // serde_json::to_string(&get_foreign_module_details()).expect("foreign_module serialization failed"), + // ).expect("Failed to write JSON to file"); + } else { + None + }; + + let called_functions = calls_map.into_iter().map(|(k,(_,name))| (k,name)).collect::>(); + let allocs = visited_allocs.into_iter().collect::>(); let crate_id = tcx.stable_crate_id(LOCAL_CRATE).as_u64(); - let json_items = serde_json::to_value(&items).expect("serde_json mono items to value failed"); - write!(writer, "{{\"name\": {}, \"crate_id\": {}, \"allocs\": {}, \"functions\": {}, \"uneval_consts\": {}, \"items\": {}", - serde_json::to_string(&local_crate.name).expect("serde_json string to json failed"), - serde_json::to_string(&crate_id).expect("serde_json number to json failed"), - serde_json::to_string(&allocs).expect("serde_json global allocs to json failed"), - serde_json::to_string(&called_functions).expect("serde_json functions to json failed"), - serde_json::to_string(&unevaluated_consts).expect("serde_json unevaluated consts to json failed"), - serde_json::to_string(&json_items).expect("serde_json mono items to json failed"), - ).expect("Failed to write JSON to file"); - if debug_enabled() { - let fn_sources = calls_map.iter().map(|(k,(source,_))| (k,source)).collect::>(); - write!(writer, ",\"fn_sources\": {}, \"types\": {}, \"foreign_modules\": {}}}", - serde_json::to_string(&fn_sources).expect("serde_json functions failed"), - serde_json::to_string(&visited_tys).expect("serde_json tys failed"), - serde_json::to_string(&get_foreign_module_details()).expect("foreign_module serialization failed"), - ).expect("Failed to write JSON to file"); - } else { - write!(writer, "}}").expect("Failed to write JSON to file"); - } + + let result: SmirJson = SmirJson { + name: local_crate.name, + crate_id: crate_id, + allocs, + functions: called_functions, + uneval_consts: unevaluated_consts.into_iter().collect(), + items, + debug + }; + + write!(writer, "{}", serde_json::to_string(&result).expect("serde_json failed to write result")).unwrap(); + } pub fn emit_smir(tcx: TyCtxt<'_>) { From 6f1020f68fb2d225c3e85c19c7271d5f2790038f Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Thu, 23 Jan 2025 15:34:54 +1100 Subject: [PATCH 02/17] move writing the file out of the collection function --- src/printer.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index b9b5b93..6b77739 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,3 +1,4 @@ +use std::io::Write; use std::{collections::HashMap,fs::File,io,iter::Iterator,vec::Vec,str,}; extern crate rustc_middle; extern crate rustc_monomorphize; @@ -738,7 +739,7 @@ struct SmirJsonDebugInfo<'t> { // Serialization Entrypoint // ======================== -fn emit_smir_internal(tcx: TyCtxt<'_>, writer: &mut dyn io::Write) { +fn collect_smir(tcx: TyCtxt<'_>) -> SmirJson { let local_crate = stable_mir::local_crate(); let items = collect_items(tcx); let items_clone = items.clone(); @@ -775,7 +776,7 @@ fn emit_smir_internal(tcx: TyCtxt<'_>, writer: &mut dyn io::Write) { let allocs = visited_allocs.into_iter().collect::>(); let crate_id = tcx.stable_crate_id(LOCAL_CRATE).as_u64(); - let result: SmirJson = SmirJson { + SmirJson { name: local_crate.name, crate_id: crate_id, allocs, @@ -783,21 +784,19 @@ fn emit_smir_internal(tcx: TyCtxt<'_>, writer: &mut dyn io::Write) { uneval_consts: unevaluated_consts.into_iter().collect(), items, debug - }; - - write!(writer, "{}", serde_json::to_string(&result).expect("serde_json failed to write result")).unwrap(); + } } pub fn emit_smir(tcx: TyCtxt<'_>) { + + let smir_json = serde_json::to_string(&collect_smir(tcx)).expect("serde_json failed to write result"); + match tcx.output_filenames(()).path(OutputType::Mir) { - OutFileName::Stdout => { - let mut f = io::stdout(); - emit_smir_internal(tcx, &mut f); - } + OutFileName::Stdout => { write!(&io::stdout(), "{}", smir_json).expect("Failed to write smir.json"); } OutFileName::Real(path) => { - let mut f = io::BufWriter::new(File::create(&path.with_extension("smir.json")).expect("Failed to create SMIR output file")); - emit_smir_internal(tcx, &mut f); + let mut b = io::BufWriter::new(File::create(&path.with_extension("smir.json")).expect("Failed to create {path}.smir.json output file")); + write!(b, "{}", smir_json).expect("Failed to write smir.json"); } } } From 541c225920abc3e882f19967ecf77f5a5123f1ab Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Thu, 23 Jan 2025 16:00:29 +1100 Subject: [PATCH 03/17] formatting --- src/printer.rs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index 6b77739..4ba626d 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -16,7 +16,7 @@ use rustc_middle::ty::{TyCtxt, Ty, TyKind, EarlyBinder, FnSig, GenericArgs, Type use rustc_session::config::{OutFileName, OutputType}; use rustc_span::{def_id::{DefId, LOCAL_CRATE}, symbol}; use rustc_smir::rustc_internal; -use stable_mir::{ +use stable_mir::{ CrateItem, CrateDef, ItemKind, @@ -493,7 +493,7 @@ fn collect_ty(val_collector: &mut InternedValueCollector, val: stable_mir::ty::T Some(val.layout()) } }; - + let maybe_layout_shape = if let Some(Ok(layout)) = maybe_layout { Some(layout.shape()) } else { @@ -743,8 +743,10 @@ fn collect_smir(tcx: TyCtxt<'_>) -> SmirJson { let local_crate = stable_mir::local_crate(); let items = collect_items(tcx); let items_clone = items.clone(); - let (unevaluated_consts, mut items) = collect_unevaluated_constant_items(tcx, items); - let (calls_map, visited_allocs, visited_tys) = collect_interned_values(tcx, items.iter().map(|i| &i.mono_item).collect::>()); + let (unevaluated_consts, mut items) = + collect_unevaluated_constant_items(tcx, items); + let (calls_map, visited_allocs, visited_tys) = + collect_interned_values(tcx, items.iter().map(|i| &i.mono_item).collect::>()); // FIXME: We dump extra static items here --- this should be handled better for (_, alloc) in visited_allocs.iter() { @@ -758,10 +760,10 @@ fn collect_smir(tcx: TyCtxt<'_>) -> SmirJson { } } - let calls_clone = calls_map.clone(); let debug: Option = if debug_enabled() { - let fn_sources = calls_clone.into_iter().map(|(k,(source,_))| (k,source)).collect::>(); + let fn_sources = + calls_map.clone().into_iter().map(|(k,(source,_))| (k,source)).collect::>(); Some(SmirJsonDebugInfo { fn_sources, types: visited_tys, foreign_modules: get_foreign_module_details()}) // write!(writer, "{{ \"fn_sources\": {}, \"types\": {}, \"foreign_modules\": {}}}", // serde_json::to_string(&fn_sources).expect("serde_json functions failed"), @@ -772,9 +774,12 @@ fn collect_smir(tcx: TyCtxt<'_>) -> SmirJson { None }; - let called_functions = calls_map.into_iter().map(|(k,(_,name))| (k,name)).collect::>(); - let allocs = visited_allocs.into_iter().collect::>(); - let crate_id = tcx.stable_crate_id(LOCAL_CRATE).as_u64(); + let called_functions = + calls_map.into_iter().map(|(k,(_,name))| (k,name)).collect::>(); + let allocs = + visited_allocs.into_iter().collect::>(); + let crate_id = + tcx.stable_crate_id(LOCAL_CRATE).as_u64(); SmirJson { name: local_crate.name, @@ -793,9 +798,14 @@ pub fn emit_smir(tcx: TyCtxt<'_>) { let smir_json = serde_json::to_string(&collect_smir(tcx)).expect("serde_json failed to write result"); match tcx.output_filenames(()).path(OutputType::Mir) { - OutFileName::Stdout => { write!(&io::stdout(), "{}", smir_json).expect("Failed to write smir.json"); } + OutFileName::Stdout => { + write!(&io::stdout(), "{}", smir_json).expect("Failed to write smir.json"); + } OutFileName::Real(path) => { - let mut b = io::BufWriter::new(File::create(&path.with_extension("smir.json")).expect("Failed to create {path}.smir.json output file")); + let mut b = + io::BufWriter::new( + File::create(&path.with_extension("smir.json")) + .expect("Failed to create {path}.smir.json output file")); write!(b, "{}", smir_json).expect("Failed to write smir.json"); } } From ba0c192dc040132cb6f42e44c5133135e0a6002d Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Thu, 23 Jan 2025 17:23:08 +1100 Subject: [PATCH 04/17] WIP beginnings of rendering the dot file from the SmirJson --- Cargo.lock | 43 +++++++++++++++++------------- Cargo.toml | 1 + src/lib.rs | 1 + src/mk_graph.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ src/printer.rs | 14 +++++----- 5 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 src/mk_graph.rs diff --git a/Cargo.lock b/Cargo.lock index 6a288eb..434ceda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,32 +2,38 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "dot-writer" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f7a508d3f95b7cb559acf2231c7efad02fe04061d3165b12513c2dbcc77af0" + [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -36,14 +42,15 @@ dependencies = [ name = "smir_pretty" version = "0.1.0" dependencies = [ + "dot-writer", "tracing", ] [[package]] name = "syn" -version = "2.0.87" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -52,9 +59,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -63,9 +70,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -74,15 +81,15 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/Cargo.toml b/Cargo.toml index ec09317..721edc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +dot-writer = "0.1.4" tracing = "0.1" # serde = { version = "=1.0.202", features = ["derive"] } # serde_cbor = "0.11" diff --git a/src/lib.rs b/src/lib.rs index f17c342..86ca158 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![feature(rustc_private)] pub mod driver; +pub mod mk_graph; pub mod printer; pub use driver::stable_mir_driver; pub use printer::*; diff --git a/src/mk_graph.rs b/src/mk_graph.rs new file mode 100644 index 0000000..090ca7a --- /dev/null +++ b/src/mk_graph.rs @@ -0,0 +1,70 @@ +use std::hash::{DefaultHasher, Hash, Hasher}; + +use crate::printer::{SmirJson}; + +use dot_writer::{Color, DotWriter, Attributes, Shape, Style}; + +impl SmirJson<'_> { + + pub fn to_dot_file(self) -> String { + let mut bytes = Vec::new(); + + { + let mut writer = DotWriter::from(&mut bytes); + + writer.set_pretty_print(true); + + writer.digraph().set_label(&self.name[..]); + } + + String::from_utf8(bytes).expect("Error converting dot file") + } + +} +/// consistently naming function clusters +fn short_name(function_name: String) -> String { + let mut h = DefaultHasher::new(); + function_name.hash(&mut h); + format!("X{:x}", h.finish()) +} + +/// consistently naming block nodes in function clusters +fn block_name(function_name: String, id: usize) -> String { + let mut h = DefaultHasher::new(); + function_name.hash(&mut h); + format!("X{:x}_{}", h.finish(), id) +} + + +#[test] +fn test_graphviz() { + + use std::io; + + let name = "fubar"; + + let mut w = io::stdout(); + + let mut writer: DotWriter = DotWriter::from(&mut w); + + writer.set_pretty_print(true); + + let mut digraph = writer.digraph(); + + digraph + .node_attributes() + .set_shape(Shape::Rectangle) + .set_label(name); + + digraph.node_auto().set_label(name); + + let mut cluster = digraph.cluster(); + + cluster + .set_label("cluck cluck") + .set_color(Color::Blue); + cluster.node_named(short_name("cluck_1".to_string())); + cluster.node_named(block_name("cluck_2".to_string(), 2)); + cluster.edge(short_name("cluck_1".to_string()), block_name("cluck_2".to_string(), 2)); + +} \ No newline at end of file diff --git a/src/printer.rs b/src/printer.rs index 4ba626d..408457c 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -720,13 +720,13 @@ fn collect_items(tcx: TyCtxt<'_>) -> HashMap { /// the serialised data structure as a whole #[derive(Serialize)] pub struct SmirJson<'t> { - name: String, - crate_id: u64, - allocs: Vec<(AllocId,AllocInfo)>, - functions: Vec<(LinkMapKey<'t>, FnSymType)>, - uneval_consts: Vec<(ConstDef, String)>, - items: Vec, - debug: Option> + pub name: String, + pub crate_id: u64, + pub allocs: Vec<(AllocId,AllocInfo)>, + pub functions: Vec<(LinkMapKey<'t>, FnSymType)>, + pub uneval_consts: Vec<(ConstDef, String)>, + pub items: Vec, + pub debug: Option> } #[derive(Serialize)] From d7566bd5c7bf27995a2660d400f6f27a3f1ebece Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Fri, 24 Jan 2025 12:22:37 +1100 Subject: [PATCH 05/17] WIP graph generation using Rust --- src/mk_graph.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++--- src/printer.rs | 14 +++---- 2 files changed, 101 insertions(+), 13 deletions(-) diff --git a/src/mk_graph.rs b/src/mk_graph.rs index 090ca7a..3fa622d 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -1,8 +1,13 @@ -use std::hash::{DefaultHasher, Hash, Hasher}; +use std::{collections::HashMap, hash::{DefaultHasher, Hash, Hasher}}; -use crate::printer::{SmirJson}; +extern crate stable_mir; -use dot_writer::{Color, DotWriter, Attributes, Shape, Style}; +use stable_mir::ty::Ty; +use stable_mir::mir::{BasicBlock, Statement, TerminatorKind}; + +use crate::{printer::{FnSymType, SmirJson}, MonoItemKind}; + +use dot_writer::{DotWriter, Attributes, Scope}; impl SmirJson<'_> { @@ -14,22 +19,105 @@ impl SmirJson<'_> { writer.set_pretty_print(true); - writer.digraph().set_label(&self.name[..]); + let mut graph = writer.digraph(); + graph.set_label(&self.name[..]); + + let func_map: HashMap = + self.functions + .into_iter() + .map(|(k,v)| (k.0, mk_string(v))) + .collect(); + + for item in self.items { + match item.mono_item_kind { + MonoItemKind::MonoItemFn{ name, body, id: _} => { + let mut c = graph.cluster(); + c.set_label(&name[..]); + + fn process_block<'a,'b>(name: &String, cluster:&mut Scope<'a,'b>, node_id: usize, b: &BasicBlock) { + let mut n = cluster.node_named(block_name(name, node_id)); + // TODO: render statements and terminator as text label (with line breaks) + n.set_label("the Terminator"); + // switch on terminator kind, add inner and out-edges according to terminator + use TerminatorKind::*; + match b.terminator.kind { + + Goto{target: _} => {}, + SwitchInt{discr:_, targets: _} => {}, + Resume{} => {}, + Abort{} => {}, + Return{} => {}, + Unreachable{} => {}, + TerminatorKind::Drop{place: _, target: _, unwind: _} => {}, + Call{func: _, args: _, destination: _, target: _, unwind: _} => {}, + Assert{target: _, ..} => {}, + InlineAsm{destination: _, ..} => {} + } + } + + fn process_blocks<'a,'b>(name: &String, cluster:&mut Scope<'a,'b>, offset: usize, blocks: &Vec) { + let mut n = offset; + for b in blocks { + process_block(name, cluster, n, b); + n += 1; + } + } + + + match body.len() { + 0 => { + c.node_auto().set_label(""); + }, + 1 => { + process_blocks(&item.symbol_name, &mut c, 0, &body[0].blocks); + } + _more => { + let mut curr: usize = 0; + for b in body { + let mut cc = c.cluster(); + process_blocks(&item.symbol_name, &mut cc, curr, &b.blocks); + curr += b.blocks.len(); + } + } + } + + } + MonoItemKind::MonoItemGlobalAsm { asm } => { + let mut n = graph.node_named(short_name(&asm)); + n.set_label(&asm.lines().collect::()[..]); + } + MonoItemKind::MonoItemStatic { name, id: _, allocation: _ } => { + let mut n = graph.node_named(short_name(&name)); + n.set_label(&name[..]); + } + } + + } + } String::from_utf8(bytes).expect("Error converting dot file") } } + +fn mk_string(f: FnSymType) -> String { + match f { + FnSymType::NormalSym(name) => name, + FnSymType::NoOpSym(name) => format!("NoOp: {name}"), + FnSymType::IntrinsicSym(name) => format!("Intr: {name}"), + } +} + /// consistently naming function clusters -fn short_name(function_name: String) -> String { +fn short_name(function_name: &String) -> String { let mut h = DefaultHasher::new(); function_name.hash(&mut h); format!("X{:x}", h.finish()) } /// consistently naming block nodes in function clusters -fn block_name(function_name: String, id: usize) -> String { +fn block_name(function_name: &String, id: usize) -> String { let mut h = DefaultHasher::new(); function_name.hash(&mut h); format!("X{:x}_{}", h.finish(), id) diff --git a/src/printer.rs b/src/printer.rs index 408457c..1e55b81 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -226,7 +226,7 @@ fn hash(obj: T) -> u64 { // ========================================================= #[derive(Serialize, Clone)] -enum MonoItemKind { +pub enum MonoItemKind { MonoItemFn { name: String, id: stable_mir::DefId, @@ -242,11 +242,11 @@ enum MonoItemKind { }, } #[derive(Serialize, Clone)] -struct Item { +pub struct Item { #[serde(skip)] mono_item: MonoItem, - symbol_name: String, - mono_item_kind: MonoItemKind, + pub symbol_name: String, + pub mono_item_kind: MonoItemKind, details: Option, } @@ -300,7 +300,7 @@ fn mk_item(tcx: TyCtxt<'_>, item: MonoItem, sym_name: String) -> Item { // ========================== #[derive(Debug, Clone, Serialize, PartialEq, Eq)] -enum FnSymType { +pub enum FnSymType { NoOpSym(String), IntrinsicSym(String), NormalSym(String), @@ -330,7 +330,7 @@ fn fn_inst_sym<'tcx>(tcx: TyCtxt<'tcx>, ty: Option, inst: Op } #[derive(Clone, Debug, Eq, Hash, PartialEq)] -struct LinkMapKey<'tcx>(stable_mir::ty::Ty, Option>); +pub struct LinkMapKey<'tcx>(pub stable_mir::ty::Ty, Option>); impl Serialize for LinkMapKey<'_> { fn serialize(&self, serializer: S) -> Result @@ -730,7 +730,7 @@ pub struct SmirJson<'t> { } #[derive(Serialize)] -struct SmirJsonDebugInfo<'t> { +pub struct SmirJsonDebugInfo<'t> { fn_sources: Vec<(LinkMapKey<'t>,ItemSource)>, types: TyMap, foreign_modules: Vec<(String, Vec)> From 041de465e13ffc094755ff81e10b3db08306535b Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Fri, 24 Jan 2025 15:47:14 +1100 Subject: [PATCH 06/17] graph generation WIP, control structures done --- src/mk_graph.rs | 195 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 135 insertions(+), 60 deletions(-) diff --git a/src/mk_graph.rs b/src/mk_graph.rs index 3fa622d..eca5bd2 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -3,7 +3,15 @@ use std::{collections::HashMap, hash::{DefaultHasher, Hash, Hasher}}; extern crate stable_mir; use stable_mir::ty::Ty; -use stable_mir::mir::{BasicBlock, Statement, TerminatorKind}; +use stable_mir::mir::{ + BasicBlock, + ConstOperand, + Operand, + Place, + Statement, + TerminatorKind, + UnwindAction, +}; use crate::{printer::{FnSymType, SmirJson}, MonoItemKind}; @@ -25,7 +33,7 @@ impl SmirJson<'_> { let func_map: HashMap = self.functions .into_iter() - .map(|(k,v)| (k.0, mk_string(v))) + .map(|(k,v)| (k.0, function_string(v))) .collect(); for item in self.items { @@ -34,48 +42,137 @@ impl SmirJson<'_> { let mut c = graph.cluster(); c.set_label(&name[..]); - fn process_block<'a,'b>(name: &String, cluster:&mut Scope<'a,'b>, node_id: usize, b: &BasicBlock) { - let mut n = cluster.node_named(block_name(name, node_id)); + // Cannot define local functions that capture env. variables. Instead we define _closures_. + let process_block = |cluster:&mut Scope<'_,'_>, node_id: usize, b: &BasicBlock | { + let name = &item.symbol_name; + //fn process_block<'a,'b>(name: &String, cluster:&mut Scope<'a,'b>, node_id: usize, b: &BasicBlock) { + let this_block = block_name(name, node_id); + let mut n = cluster.node_named(&this_block); // TODO: render statements and terminator as text label (with line breaks) - n.set_label("the Terminator"); // switch on terminator kind, add inner and out-edges according to terminator use TerminatorKind::*; - match b.terminator.kind { - - Goto{target: _} => {}, - SwitchInt{discr:_, targets: _} => {}, - Resume{} => {}, - Abort{} => {}, - Return{} => {}, - Unreachable{} => {}, - TerminatorKind::Drop{place: _, target: _, unwind: _} => {}, - Call{func: _, args: _, destination: _, target: _, unwind: _} => {}, - Assert{target: _, ..} => {}, - InlineAsm{destination: _, ..} => {} + match &b.terminator.kind { + + Goto{target} => { + n.set_label("Goto"); + drop(n); // so we can borro `cluster` again below + cluster.edge(&this_block, block_name(name, *target)); + }, + SwitchInt{discr:_, targets} => { + n.set_label("SwitchInt"); + drop(n); // so we can borrow `cluster` again below + for (d,t) in targets.clone().branches() { + cluster + .edge(&this_block, block_name(name, t)) + .attributes() + .set_label(&format!("{d}")); + } + }, + Resume{} => { + n.set_label("Resume"); + }, + Abort{} => { + n.set_label("Abort"); + }, + Return{} => { + n.set_label("Return"); + }, + Unreachable{} => { + n.set_label("Unreachable"); + }, + TerminatorKind::Drop{place, target, unwind} => { + n.set_label(&format!("Drop {}", show_place(place))); + drop(n); + if let UnwindAction::Cleanup(t) = unwind { + cluster + .edge(&this_block, block_name(name, *t)) + .attributes() + .set_label("Cleanup"); + } + cluster + .edge(&this_block, block_name(name, *target)); + }, + Call{func, args, destination, target, unwind} => { + n.set_label(&format!("Call()")); + drop(n); + if let UnwindAction::Cleanup(t) = unwind { + cluster + .edge(&this_block, block_name(name, *t)) + .attributes() + .set_label("Cleanup"); + } + if let Some(t) = target { + let dest = show_place(destination); + cluster + .edge(&this_block, block_name(name, *t)) + .attributes() + .set_label(&dest); + } + let e = match func { + Operand::Constant(ConstOperand{const_, ..}) => { + if let Some(callee) = func_map.get(&const_.ty()) { + cluster + .edge(&this_block, block_name(callee, 0)) + } else { + let unknown = format!("{}", const_.ty()); + cluster + .edge(&this_block, unknown) + } + }, + Operand::Copy(place) => { + cluster.edge(&this_block, format!("{}: {}", &this_block, show_place(place))) + }, + Operand::Move(place) => { + cluster.edge(&this_block, format!("{}: {}", &this_block, show_place(place))) + }, + }; + let arg_str = args.into_iter().map(show_op).collect::>().join(","); + e.attributes().set_label(&arg_str); + + }, + Assert{target, ..} => { + n.set_label("Assert"); + drop(n); + cluster + .edge(&this_block, block_name(name, *target)); + }, + InlineAsm{destination, unwind,..} => { + n.set_label("Inline ASM"); + drop(n); + if let Some(t) = destination { + cluster + .edge(&this_block, block_name(name, *t)); + } + if let UnwindAction::Cleanup(t) = unwind { + cluster + .edge(&this_block, block_name(name, *t)) + .attributes() + .set_label("Cleanup"); + } + } } - } + }; - fn process_blocks<'a,'b>(name: &String, cluster:&mut Scope<'a,'b>, offset: usize, blocks: &Vec) { - let mut n = offset; + let process_blocks = |cluster:&mut Scope<'_,'_>, offset, blocks: &Vec| { + let mut n:usize = offset; for b in blocks { - process_block(name, cluster, n, b); + process_block(cluster, n, b); n += 1; } - } - + }; match body.len() { 0 => { c.node_auto().set_label(""); }, 1 => { - process_blocks(&item.symbol_name, &mut c, 0, &body[0].blocks); + process_blocks(&mut c, 0, &body[0].blocks); } _more => { let mut curr: usize = 0; for b in body { let mut cc = c.cluster(); - process_blocks(&item.symbol_name, &mut cc, curr, &b.blocks); + process_blocks(&mut cc, curr, &b.blocks); curr += b.blocks.len(); } } @@ -101,7 +198,19 @@ impl SmirJson<'_> { } -fn mk_string(f: FnSymType) -> String { +fn show_op(op: &Operand) -> String { + match op { + Operand::Constant(ConstOperand{const_, ..}) => format!("const :: {}", const_.ty()), + Operand::Copy(place) => show_place(place), + Operand::Move(place) => show_place(place), + } +} + +fn show_place(p: &Place) -> String { + format!("_{}{}", p.local, if p.projection.len() > 0 { "(...)"} else {""}) +} + +fn function_string(f: FnSymType) -> String { match f { FnSymType::NormalSym(name) => name, FnSymType::NoOpSym(name) => format!("NoOp: {name}"), @@ -122,37 +231,3 @@ fn block_name(function_name: &String, id: usize) -> String { function_name.hash(&mut h); format!("X{:x}_{}", h.finish(), id) } - - -#[test] -fn test_graphviz() { - - use std::io; - - let name = "fubar"; - - let mut w = io::stdout(); - - let mut writer: DotWriter = DotWriter::from(&mut w); - - writer.set_pretty_print(true); - - let mut digraph = writer.digraph(); - - digraph - .node_attributes() - .set_shape(Shape::Rectangle) - .set_label(name); - - digraph.node_auto().set_label(name); - - let mut cluster = digraph.cluster(); - - cluster - .set_label("cluck cluck") - .set_color(Color::Blue); - cluster.node_named(short_name("cluck_1".to_string())); - cluster.node_named(block_name("cluck_2".to_string(), 2)); - cluster.edge(short_name("cluck_1".to_string()), block_name("cluck_2".to_string(), 2)); - -} \ No newline at end of file From 8ed7e236f7ab1cc77eb8d11d0ad9b46382655415 Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Fri, 24 Jan 2025 16:27:41 +1100 Subject: [PATCH 07/17] simplistic main entry point for dot file generation --- src/main.rs | 19 +++++++++++++++++-- src/mk_graph.rs | 30 +++++++++++++++++++++++++++--- src/printer.rs | 2 +- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index d1c9700..51db8ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,23 @@ pub mod driver; pub mod printer; use driver::stable_mir_driver; use printer::emit_smir; +use smir_pretty::mk_graph::emit_dotfile; fn main() { - let args: Vec<_> = env::args().collect(); - stable_mir_driver(&args, emit_smir) + let mut args: Vec = env::args().collect(); + + match args.get(1) { + None => + stable_mir_driver(&args, emit_smir), // backward compatibility + Some(arg) if arg == "--json" => { + args.remove(1); + stable_mir_driver( &args, emit_smir) + } + Some(arg) if arg == "--dot" => { + args.remove(1); + stable_mir_driver(&args, emit_dotfile) + } + Some(_other) => + stable_mir_driver(&args, emit_smir), // backward compatibility + } } diff --git a/src/mk_graph.rs b/src/mk_graph.rs index eca5bd2..b8322dc 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -1,7 +1,14 @@ -use std::{collections::HashMap, hash::{DefaultHasher, Hash, Hasher}}; +use std::{io, io::Write, collections::HashMap, fs::File, hash::{DefaultHasher, Hash, Hasher}}; + +use dot_writer::{DotWriter, Attributes, Scope}; + +extern crate rustc_middle; +use rustc_middle::ty::TyCtxt; extern crate stable_mir; +use rustc_session::config::{OutFileName, OutputType}; +extern crate rustc_session; use stable_mir::ty::Ty; use stable_mir::mir::{ BasicBlock, @@ -13,9 +20,26 @@ use stable_mir::mir::{ UnwindAction, }; -use crate::{printer::{FnSymType, SmirJson}, MonoItemKind}; +use crate::{printer::{FnSymType, SmirJson, collect_smir}, MonoItemKind}; -use dot_writer::{DotWriter, Attributes, Scope}; +// entry point to write the dot file +pub fn emit_dotfile(tcx: TyCtxt<'_>) { + + let smir_dot = collect_smir(tcx).to_dot_file(); + + match tcx.output_filenames(()).path(OutputType::Mir) { + OutFileName::Stdout => { + write!(io::stdout(), "{}", smir_dot).expect("Failed to write smir.dot"); + } + OutFileName::Real(path) => { + let mut b = + io::BufWriter::new( + File::create(&path.with_extension("smir.dot")) + .expect("Failed to create {path}.smir.dot output file")); + write!(b, "{}", smir_dot).expect("Failed to write smir.dot"); + } + } +} impl SmirJson<'_> { diff --git a/src/printer.rs b/src/printer.rs index 1e55b81..cb50439 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -739,7 +739,7 @@ pub struct SmirJsonDebugInfo<'t> { // Serialization Entrypoint // ======================== -fn collect_smir(tcx: TyCtxt<'_>) -> SmirJson { +pub fn collect_smir(tcx: TyCtxt<'_>) -> SmirJson { let local_crate = stable_mir::local_crate(); let items = collect_items(tcx); let items_clone = items.clone(); From 9fac43c3fb8523d4426321716e81bb31ff8dfdcb Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Fri, 24 Jan 2025 17:03:23 +1100 Subject: [PATCH 08/17] For posterity: non-working variant to insert illegal call edges outside cluster --- src/mk_graph.rs | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/mk_graph.rs b/src/mk_graph.rs index b8322dc..964eff2 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -1,6 +1,6 @@ -use std::{io, io::Write, collections::HashMap, fs::File, hash::{DefaultHasher, Hash, Hasher}}; +use std::{collections::{HashMap, HashSet}, fs::File, hash::{DefaultHasher, Hash, Hasher}, io::{self, Write}}; -use dot_writer::{DotWriter, Attributes, Scope}; +use dot_writer::{Attributes, Color, DotWriter, Scope}; extern crate rustc_middle; use rustc_middle::ty::TyCtxt; @@ -60,6 +60,22 @@ impl SmirJson<'_> { .map(|(k,v)| (k.0, function_string(v))) .collect(); + let item_names: HashSet = + self.items + .iter() + .map(|i| i.symbol_name.clone()) + .collect(); + + // first create all nodes for functions not in the items list + for f in func_map.values() { + if ! item_names.contains(f) { + graph + .node_named(block_name(f, 0)) + .set_label(f) + .set_color(Color::Red); + } + } + for item in self.items { match item.mono_item_kind { MonoItemKind::MonoItemFn{ name, body, id: _} => { @@ -132,22 +148,31 @@ impl SmirJson<'_> { .attributes() .set_label(&dest); } + + drop(cluster); // done within cluster, call edge outside of cluster + let e = match func { Operand::Constant(ConstOperand{const_, ..}) => { if let Some(callee) = func_map.get(&const_.ty()) { - cluster + // if ! item_names.contains(callee) { + // graph + // .node_named(block_name(callee, 0)) + // .set_label(callee); + // } + graph .edge(&this_block, block_name(callee, 0)) } else { let unknown = format!("{}", const_.ty()); - cluster + // graph.node_named(&unknown); + graph .edge(&this_block, unknown) } }, Operand::Copy(place) => { - cluster.edge(&this_block, format!("{}: {}", &this_block, show_place(place))) + graph.edge(&this_block, format!("{}: {}", &this_block, show_place(place))) }, Operand::Move(place) => { - cluster.edge(&this_block, format!("{}: {}", &this_block, show_place(place))) + graph.edge(&this_block, format!("{}: {}", &this_block, show_place(place))) }, }; let arg_str = args.into_iter().map(show_op).collect::>().join(","); From db5e4045a1a2c6af69a202a65fa1e9680934fbfd Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Fri, 24 Jan 2025 17:28:09 +1100 Subject: [PATCH 09/17] Thanks for borrowing --- src/mk_graph.rs | 96 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/src/mk_graph.rs b/src/mk_graph.rs index 964eff2..ddbfedd 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -132,7 +132,7 @@ impl SmirJson<'_> { cluster .edge(&this_block, block_name(name, *target)); }, - Call{func, args, destination, target, unwind} => { + Call{func: _, args: _, destination, target, unwind} => { n.set_label(&format!("Call()")); drop(n); if let UnwindAction::Cleanup(t) = unwind { @@ -149,35 +149,8 @@ impl SmirJson<'_> { .set_label(&dest); } - drop(cluster); // done within cluster, call edge outside of cluster - - let e = match func { - Operand::Constant(ConstOperand{const_, ..}) => { - if let Some(callee) = func_map.get(&const_.ty()) { - // if ! item_names.contains(callee) { - // graph - // .node_named(block_name(callee, 0)) - // .set_label(callee); - // } - graph - .edge(&this_block, block_name(callee, 0)) - } else { - let unknown = format!("{}", const_.ty()); - // graph.node_named(&unknown); - graph - .edge(&this_block, unknown) - } - }, - Operand::Copy(place) => { - graph.edge(&this_block, format!("{}: {}", &this_block, show_place(place))) - }, - Operand::Move(place) => { - graph.edge(&this_block, format!("{}: {}", &this_block, show_place(place))) - }, - }; - let arg_str = args.into_iter().map(show_op).collect::>().join(","); - e.attributes().set_label(&arg_str); - + // The call edge has to be drawn outside the cluster, and therefore outside this function! + // I have to separate this code into its own second function. }, Assert{target, ..} => { n.set_label("Assert"); @@ -210,7 +183,7 @@ impl SmirJson<'_> { } }; - match body.len() { + match &body.len() { 0 => { c.node_auto().set_label(""); }, @@ -219,13 +192,72 @@ impl SmirJson<'_> { } _more => { let mut curr: usize = 0; - for b in body { + for b in &body { let mut cc = c.cluster(); process_blocks(&mut cc, curr, &b.blocks); curr += b.blocks.len(); } } } + drop(c); // so we can borrow graph again + + // call edges have to be added _outside_ the cluster of blocks for one function + // because they go between different clusters. Due to a scope/borrow issue, we have + // to make a 2nd pass over the bodies of the item. + let add_call_edges = | graph: &mut Scope<'_,'_>, offset: usize, bs: &Vec | { + for (i, b) in bs.iter().enumerate() { + let this_block = block_name(&item.symbol_name, offset + i); + + match &b.terminator.kind { + TerminatorKind::Call{func, args, ..} => { + let e = match func { + Operand::Constant(ConstOperand{const_, ..}) => { + if let Some(callee) = func_map.get(&const_.ty()) { + // if ! item_names.contains(callee) { + // graph + // .node_named(block_name(callee, 0)) + // .set_label(callee); + // } + graph + .edge(&this_block, block_name(callee, 0)) + } else { + let unknown = format!("{}", const_.ty()); + // graph.node_named(&unknown); + graph + .edge(&this_block, unknown) + } + }, + Operand::Copy(place) => { + graph.edge(&this_block, format!("{}: {}", &this_block, show_place(place))) + }, + Operand::Move(place) => { + graph.edge(&this_block, format!("{}: {}", &this_block, show_place(place))) + }, + }; + let arg_str = args.into_iter().map(show_op).collect::>().join(","); + e.attributes().set_label(&arg_str); + + }, + _other => { + // nothing to do + }, + } + } + }; + + match &body.len() { + 0 => {}, + 1 => { + add_call_edges(&mut graph, 0, &body[0].blocks); + } + _more => { + let mut curr: usize = 0; + for b in &body { + add_call_edges(&mut graph, curr, &b.blocks); + curr += b.blocks.len(); + } + } + } } MonoItemKind::MonoItemGlobalAsm { asm } => { From 8cb106c1b6fda7ce6f36f4be540226d1a6cd0811 Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Fri, 24 Jan 2025 17:55:12 +1100 Subject: [PATCH 10/17] render long cluster names in multiple lines --- src/mk_graph.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/mk_graph.rs b/src/mk_graph.rs index ddbfedd..60174c3 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -71,7 +71,7 @@ impl SmirJson<'_> { if ! item_names.contains(f) { graph .node_named(block_name(f, 0)) - .set_label(f) + .set_label(&name_lines(f)) .set_color(Color::Red); } } @@ -80,7 +80,7 @@ impl SmirJson<'_> { match item.mono_item_kind { MonoItemKind::MonoItemFn{ name, body, id: _} => { let mut c = graph.cluster(); - c.set_label(&name[..]); + c.set_label(&name_lines(&name)); // Cannot define local functions that capture env. variables. Instead we define _closures_. let process_block = |cluster:&mut Scope<'_,'_>, node_id: usize, b: &BasicBlock | { @@ -299,6 +299,15 @@ fn function_string(f: FnSymType) -> String { } } +fn name_lines(name: &String) -> String { + name + .split_inclusive(" ") + .flat_map(|s| s.split_inclusive("::")) + .map(|s| s.to_string()) + .collect::>() + .join("\\n") +} + /// consistently naming function clusters fn short_name(function_name: &String) -> String { let mut h = DefaultHasher::new(); From 94f7b6d5423e29bf8c24b817b473df3fa4ef3772 Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Fri, 24 Jan 2025 18:03:17 +1100 Subject: [PATCH 11/17] some filling --- src/mk_graph.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/mk_graph.rs b/src/mk_graph.rs index 60174c3..b8ed530 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -1,6 +1,6 @@ use std::{collections::{HashMap, HashSet}, fs::File, hash::{DefaultHasher, Hash, Hasher}, io::{self, Write}}; -use dot_writer::{Attributes, Color, DotWriter, Scope}; +use dot_writer::{Attributes, Color, DotWriter, Scope, Style}; extern crate rustc_middle; use rustc_middle::ty::TyCtxt; @@ -81,6 +81,10 @@ impl SmirJson<'_> { MonoItemKind::MonoItemFn{ name, body, id: _} => { let mut c = graph.cluster(); c.set_label(&name_lines(&name)); + if is_unqualified(&name) { + c.set_style(Style::Filled); + c.set_color(Color::LightGrey); + } // Cannot define local functions that capture env. variables. Instead we define _closures_. let process_block = |cluster:&mut Scope<'_,'_>, node_id: usize, b: &BasicBlock | { @@ -291,6 +295,10 @@ fn show_place(p: &Place) -> String { format!("_{}{}", p.local, if p.projection.len() > 0 { "(...)"} else {""}) } +fn is_unqualified(name: &String) -> bool { + ! name.contains("::") +} + fn function_string(f: FnSymType) -> String { match f { FnSymType::NormalSym(name) => name, From f548d537a83d917038a7f17a49bce1bad671948c Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Tue, 28 Jan 2025 10:53:12 +1100 Subject: [PATCH 12/17] kill commented old code --- src/printer.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index cb50439..0db98ff 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -765,11 +765,6 @@ pub fn collect_smir(tcx: TyCtxt<'_>) -> SmirJson { let fn_sources = calls_map.clone().into_iter().map(|(k,(source,_))| (k,source)).collect::>(); Some(SmirJsonDebugInfo { fn_sources, types: visited_tys, foreign_modules: get_foreign_module_details()}) - // write!(writer, "{{ \"fn_sources\": {}, \"types\": {}, \"foreign_modules\": {}}}", - // serde_json::to_string(&fn_sources).expect("serde_json functions failed"), - // serde_json::to_string(&visited_tys).expect("serde_json tys failed"), - // serde_json::to_string(&get_foreign_module_details()).expect("foreign_module serialization failed"), - // ).expect("Failed to write JSON to file"); } else { None }; From 88204cc117e9a22acc16b4cccef96db4da739ed1 Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Tue, 28 Jan 2025 10:55:45 +1100 Subject: [PATCH 13/17] kill commented old code --- src/mk_graph.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mk_graph.rs b/src/mk_graph.rs index b8ed530..da22107 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -89,7 +89,6 @@ impl SmirJson<'_> { // Cannot define local functions that capture env. variables. Instead we define _closures_. let process_block = |cluster:&mut Scope<'_,'_>, node_id: usize, b: &BasicBlock | { let name = &item.symbol_name; - //fn process_block<'a,'b>(name: &String, cluster:&mut Scope<'a,'b>, node_id: usize, b: &BasicBlock) { let this_block = block_name(name, node_id); let mut n = cluster.node_named(&this_block); // TODO: render statements and terminator as text label (with line breaks) From 8131fcd09b44323b63c704462a9affc8048a5278 Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Tue, 28 Jan 2025 11:00:47 +1100 Subject: [PATCH 14/17] adjust some comments --- src/mk_graph.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/mk_graph.rs b/src/mk_graph.rs index da22107..6438a8e 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -152,8 +152,8 @@ impl SmirJson<'_> { .set_label(&dest); } - // The call edge has to be drawn outside the cluster, and therefore outside this function! - // I have to separate this code into its own second function. + // The call edge has to be drawn outside the cluster, outside this function (cluster borrows &mut graph)! + // Code for that is therefore separated into its own second function below. }, Assert{target, ..} => { n.set_label("Assert"); @@ -216,16 +216,13 @@ impl SmirJson<'_> { let e = match func { Operand::Constant(ConstOperand{const_, ..}) => { if let Some(callee) = func_map.get(&const_.ty()) { - // if ! item_names.contains(callee) { - // graph - // .node_named(block_name(callee, 0)) - // .set_label(callee); - // } + // callee node/body will be added when its body is added, missing ones added before graph .edge(&this_block, block_name(callee, 0)) } else { let unknown = format!("{}", const_.ty()); - // graph.node_named(&unknown); + // pathological case, could panic! instead. + // all unknown callees will be collapsed into one `unknown` node graph .edge(&this_block, unknown) } From 63425403cfebe95976d54408e7a6a12f0d285fa7 Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Tue, 28 Jan 2025 14:14:03 +1100 Subject: [PATCH 15/17] add fallback edge for SwitchInt --- src/mk_graph.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mk_graph.rs b/src/mk_graph.rs index 6438a8e..e1b5b4d 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -109,7 +109,11 @@ impl SmirJson<'_> { .edge(&this_block, block_name(name, t)) .attributes() .set_label(&format!("{d}")); - } + }; + cluster + .edge(&this_block, block_name(name, targets.otherwise())) + .attributes() + .set_label("other"); }, Resume{} => { n.set_label("Resume"); From 5c2c1e5952af5d329eea548a9812aba407c3bb80 Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Thu, 30 Jan 2025 14:10:29 +1100 Subject: [PATCH 16/17] Small changes from code review Co-authored-by: Daniel Cumming <124537596+dkcumming@users.noreply.github.com> --- src/main.rs | 2 +- src/mk_graph.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 51db8ed..231bec1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ fn main() { stable_mir_driver(&args, emit_smir), // backward compatibility Some(arg) if arg == "--json" => { args.remove(1); - stable_mir_driver( &args, emit_smir) + stable_mir_driver(&args, emit_smir) } Some(arg) if arg == "--dot" => { args.remove(1); diff --git a/src/mk_graph.rs b/src/mk_graph.rs index e1b5b4d..0411450 100644 --- a/src/mk_graph.rs +++ b/src/mk_graph.rs @@ -98,7 +98,7 @@ impl SmirJson<'_> { Goto{target} => { n.set_label("Goto"); - drop(n); // so we can borro `cluster` again below + drop(n); // so we can borrow `cluster` again below cluster.edge(&this_block, block_name(name, *target)); }, SwitchInt{discr:_, targets} => { From 347414d8c4ca69ff4a4f7917559bb3644f9d7b86 Mon Sep 17 00:00:00 2001 From: Jost Berthold Date: Thu, 30 Jan 2025 14:17:43 +1100 Subject: [PATCH 17/17] Briefly mention `--dot` in README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index a3f06f7..c9e7853 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,14 @@ To generate stable MIR output without building a binary, you can invoke the tool ./run.sh -Z no-codegen ``` +There is experimental support for rendering the Stable-MIR items and their basic blocks as a +call graph in graphviz' dot format. + +To produce a dot file `*.smir.dot` (instead of `*.smir.json`), one can invoke the driver with +_first_ argument `--dot`. When using `--json` as the first argument, the `*.smir.json` file +will be written. Any other strings given as first argument will be passed to the compiler +(like all subsequent arguments). + There are a few environment variables that can be set to control the tools output: 1. `LINK_ITEMS` - add entries to the link-time `functions` map for each monomorphic item in the crate;