diff options
author | Joseph Frazer <joseph.frazer@ryansg.com> | 2020-04-30 14:33:10 -0400 |
---|---|---|
committer | Joseph Frazer <joseph.frazer@ryansg.com> | 2020-05-13 08:04:48 -0400 |
commit | 43d00a8268d69bee00e0e2d9ebffd33b66d3062d (patch) | |
tree | d772323a848b00d83922ab1185233a2d13251674 /tamer | |
parent | 18d87a6b00a9e2e4e9d859320a0ca7a38c5b24a0 (diff) | |
download | tame-43d00a8268d69bee00e0e2d9ebffd33b66d3062d.tar.gz tame-43d00a8268d69bee00e0e2d9ebffd33b66d3062d.tar.bz2 tame-43d00a8268d69bee00e0e2d9ebffd33b66d3062d.zip |
[DEV-7504] Add GraphML generation
We want to be able to build a representation of the dependency graph so
we can easily inspect it.
We do not want to make GraphML by default. It is better to use a tool.
We use "petgraph-graphml".
Diffstat (limited to 'tamer')
-rw-r--r-- | tamer/Cargo.lock | 17 | ||||
-rw-r--r-- | tamer/Cargo.toml | 1 | ||||
-rw-r--r-- | tamer/src/bin/tameld.rs | 104 | ||||
-rw-r--r-- | tamer/src/ir/asg/base.rs | 5 | ||||
-rw-r--r-- | tamer/src/ir/asg/ident.rs | 63 | ||||
-rw-r--r-- | tamer/src/ir/asg/object.rs | 9 | ||||
-rw-r--r-- | tamer/src/ld/poc.rs | 58 | ||||
-rw-r--r-- | tamer/tests/tameld.rs | 13 |
8 files changed, 236 insertions, 34 deletions
diff --git a/tamer/Cargo.lock b/tamer/Cargo.lock index d40a2ef..2c4954f 100644 --- a/tamer/Cargo.lock +++ b/tamer/Cargo.lock @@ -128,6 +128,15 @@ dependencies = [ ] [[package]] +name = "petgraph-graphml" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "petgraph 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "predicates" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -247,6 +256,7 @@ dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "petgraph-graphml 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "quick-xml 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -274,6 +284,11 @@ name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "xml-rs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] "checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" "checksum assert_cmd 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b7ac5c260f75e4e4ba87b7342be6edcecbcb3eb6741a0507fda7ad115845cc65" @@ -294,6 +309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum normalize-line-endings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" "checksum petgraph 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29c127eea4a29ec6c85d153c59dc1213f33ec74cead30fe4730aecc88cc1fd92" +"checksum petgraph-graphml 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f1a24b19072d6f11cec958e21af7f80d45ee135e9d459b7703510aaf0550b47b" "checksum predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "347a1b6f0b21e636bc9872fb60b83b8e185f6f5516298b8238699f7f9a531030" "checksum predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" "checksum predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" @@ -311,3 +327,4 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum xml-rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" diff --git a/tamer/Cargo.toml b/tamer/Cargo.toml index 5060300..db72ec4 100644 --- a/tamer/Cargo.toml +++ b/tamer/Cargo.toml @@ -34,4 +34,5 @@ quick-xml = ">= 0.17.0" getopts = "0.2" exitcode = "1.1.2" lazy_static = ">= 1.4.0" +petgraph-graphml = ">= 2.0.1" diff --git a/tamer/src/bin/tameld.rs b/tamer/src/bin/tameld.rs index bf91e94..b2215f2 100644 --- a/tamer/src/bin/tameld.rs +++ b/tamer/src/bin/tameld.rs @@ -33,27 +33,41 @@ use tamer::ld::poc; /// Types of commands enum Command { - Link(String, String), + Link(String, String, Emit), Usage, } +/// Ways to emit the linked objects +enum Emit { + /// The typical desired `Emit` + /// + /// Outputs the linked object files in a format that can be used in an + /// application. + Xmle, + + /// Used for exploring the linked graph + Graphml, +} + /// Entrypoint for the linker pub fn main() -> Result<(), Box<dyn Error>> { let args: Vec<String> = env::args().collect(); let program = &args[0]; let opts = get_opts(); - let usage = opts.usage(&format!("Usage: {} -o OUTPUT FILE", program)); + let usage = + opts.usage(&format!("Usage: {} [OPTIONS] -o OUTPUT FILE", program)); match parse_options(opts, args) { - Ok(Command::Link(input, output)) => match poc::main(&input, &output) { - Err(e) => { - eprintln!("error: {}", e); - eprintln!("fatal: failed to link `{}`", output); + Ok(Command::Link(input, output, emit)) => match emit { + Emit::Xmle => poc::xmle(&input, &output), + Emit::Graphml => poc::graphml(&input, &output), + } + .or_else(|e| { + eprintln!("error: {}", e); + eprintln!("fatal: failed to link `{}`", output); - std::process::exit(1); - } - ok => ok, - }, + std::process::exit(1); + }), Ok(Command::Usage) => { println!("{}", usage); std::process::exit(exitcode::OK); @@ -77,6 +91,12 @@ fn get_opts() -> Options { let mut opts = Options::new(); opts.optopt("o", "output", "set output file name", "NAME"); opts.optflag("h", "help", "print this help menu"); + opts.optopt( + "", + "emit", + "set the output to be emitted", + "--emit xmle|graphml", + ); opts } @@ -107,7 +127,16 @@ fn parse_options(opts: Options, args: Vec<String>) -> Result<Command, Fail> { } }; - Ok(Command::Link(input, output)) + let emit = match matches.opt_str("emit") { + Some(m) => match &m[..] { + "xmle" => Emit::Xmle, + "graphml" => Emit::Graphml, + em => return Err(Fail::ArgumentMissing(format!("--emit {}", em))), + }, + None => Emit::Xmle, + }; + + Ok(Command::Link(input, output, emit)) } #[cfg(test)] @@ -208,6 +237,29 @@ mod test { } #[test] + fn parse_options_valid_long_emit_invalid() { + let opts = get_opts(); + let result = parse_options( + opts, + vec![ + String::from("program"), + String::from("foo"), + String::from("--output"), + String::from("bar"), + String::from("--emit"), + String::from("foo"), + ], + ); + + match result { + Err(Fail::ArgumentMissing(message)) => { + assert_eq!("--emit foo", message); + } + _ => panic!("Extra option not caught"), + } + } + + #[test] fn parse_options_valid() { let opts = get_opts(); let result = parse_options( @@ -221,7 +273,7 @@ mod test { ); match result { - Ok(Command::Link(infile, outfile)) => { + Ok(Command::Link(infile, outfile, Emit::Xmle)) => { assert_eq!("foo", infile); assert_eq!("bar", outfile); } @@ -239,11 +291,37 @@ mod test { String::from("foo"), String::from("--output"), String::from("bar"), + String::from("--emit"), + String::from("xmle"), + ], + ); + + match result { + Ok(Command::Link(infile, outfile, Emit::Xmle)) => { + assert_eq!("foo", infile); + assert_eq!("bar", outfile); + } + _ => panic!("Unexpected result"), + } + } + + #[test] + fn parse_options_valid_long_emit_graphml() { + let opts = get_opts(); + let result = parse_options( + opts, + vec![ + String::from("program"), + String::from("foo"), + String::from("--output"), + String::from("bar"), + String::from("--emit"), + String::from("graphml"), ], ); match result { - Ok(Command::Link(infile, outfile)) => { + Ok(Command::Link(infile, outfile, Emit::Graphml)) => { assert_eq!("foo", infile); assert_eq!("bar", outfile); } diff --git a/tamer/src/ir/asg/base.rs b/tamer/src/ir/asg/base.rs index 6dc690c..3ae8f00 100644 --- a/tamer/src/ir/asg/base.rs +++ b/tamer/src/ir/asg/base.rs @@ -97,6 +97,11 @@ where } } + /// Get the underlying Graph + pub fn into_inner(self) -> DiGraph<Node<O>, AsgEdge, Ix> { + self.graph + } + /// Index the provided symbol `name` as representing the identifier `node`. /// /// This index permits `O(1)` identifier lookups. diff --git a/tamer/src/ir/asg/ident.rs b/tamer/src/ir/asg/ident.rs index d0841d1..bab1af7 100644 --- a/tamer/src/ir/asg/ident.rs +++ b/tamer/src/ir/asg/ident.rs @@ -145,6 +145,31 @@ pub enum IdentKind { Worksheet, } +impl AsRef<str> for IdentKind { + fn as_ref(&self) -> &'static str { + match self { + Self::Cgen(_) => "cgen", + Self::Class(_) => "class", + Self::Const(_, _) => "const", + Self::Func(_, _) => "func", + Self::Gen(_, _) => "gen", + Self::Lparam(_, _) => "lparam", + Self::Param(_, _) => "param", + Self::Rate(_) => "rate", + Self::Tpl => "tpl", + Self::Type(_) => "type", + Self::MapHead => "map:head", + Self::Map => "map", + Self::MapTail => "map:tail", + Self::RetMapHead => "retmap:head", + Self::RetMap => "retmap", + Self::RetMapTail => "retmap:tail", + Self::Meta => "meta", + Self::Worksheet => "worksheet", + } + } +} + impl std::fmt::Display for IdentKind { /// Format identifier type for display to the user. /// @@ -152,31 +177,33 @@ impl std::fmt::Display for IdentKind { /// new type system, /// so for now this just uses a syntax similar to Rust. fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + let name = self.as_ref(); + match self { Self::Cgen(dim) => { - write!(fmt, "cgen[{}; {}]", DataType::Boolean, dim) + write!(fmt, "{}[{}; {}]", name, DataType::Boolean, dim) } Self::Class(dim) => { - write!(fmt, "class[{}; {}]", DataType::Boolean, dim) + write!(fmt, "{}[{}; {}]", name, DataType::Boolean, dim) + } + Self::Const(dim, dtype) => { + write!(fmt, "{}[{}; {}]", name, dtype, dim) + } + Self::Func(dim, dtype) => { + write!(fmt, "{}[{}; {}]", name, dtype, dim) + } + Self::Gen(dim, dtype) => { + write!(fmt, "{}[{}; {}]", name, dtype, dim) } - Self::Const(dim, dtype) => write!(fmt, "const[{}; {}]", dtype, dim), - Self::Func(dim, dtype) => write!(fmt, "func[{}; {}]", dtype, dim), - Self::Gen(dim, dtype) => write!(fmt, "gen[{}; {}]", dtype, dim), Self::Lparam(dim, dtype) => { - write!(fmt, "lparam[{}; {}]", dtype, dim) + write!(fmt, "{}[{}; {}]", name, dtype, dim) + } + Self::Param(dim, dtype) => { + write!(fmt, "{}[{}; {}]", name, dtype, dim) } - Self::Param(dim, dtype) => write!(fmt, "param[{}; {}]", dtype, dim), - Self::Rate(dtype) => write!(fmt, "rate[{}; 0]", dtype), - Self::Tpl => write!(fmt, "tpl"), - Self::Type(dtype) => write!(fmt, "type[{}]", dtype), - Self::MapHead => write!(fmt, "map:head"), - Self::Map => write!(fmt, "map"), - Self::MapTail => write!(fmt, "map:tail"), - Self::RetMapHead => write!(fmt, "retmap:head"), - Self::RetMap => write!(fmt, "retmap"), - Self::RetMapTail => write!(fmt, "retmap:tail"), - Self::Meta => write!(fmt, "meta"), - Self::Worksheet => write!(fmt, "worksheet"), + Self::Rate(dtype) => write!(fmt, "{}[{}; 0]", name, dtype), + Self::Type(dtype) => write!(fmt, "{}[{}]", name, dtype), + _ => write!(fmt, "{}", name), } } } diff --git a/tamer/src/ir/asg/object.rs b/tamer/src/ir/asg/object.rs index 628521e..e67799d 100644 --- a/tamer/src/ir/asg/object.rs +++ b/tamer/src/ir/asg/object.rs @@ -135,6 +135,15 @@ pub trait IdentObjectData<'i> { fn as_ident(&self) -> Option<&IdentObject<'i>>; } +impl<'i> std::fmt::Display for IdentObject<'i> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self.name() { + Some(n) => write!(f, "{}", n), + _ => write!(f, "missing"), + } + } +} + impl<'i> IdentObjectData<'i> for IdentObject<'i> { fn name(&self) -> Option<&'i Symbol<'i>> { match self { diff --git a/tamer/src/ld/poc.rs b/tamer/src/ld/poc.rs index 11f2f23..1a94558 100644 --- a/tamer/src/ld/poc.rs +++ b/tamer/src/ld/poc.rs @@ -32,6 +32,7 @@ use crate::obj::xmle::writer::XmleWriter; use crate::obj::xmlo::{AsgBuilder, AsgBuilderState, XmloReader}; use crate::sym::{DefaultInterner, Interner, Symbol}; use fxhash::FxBuildHasher; +use petgraph_graphml::GraphMl; use std::error::Error; use std::fs; use std::io::BufReader; @@ -42,7 +43,7 @@ type LinkerAsg<'i> = DefaultAsg<'i, IdentObject<'i>, global::ProgIdentSize>; type LinkerAsgBuilderState<'i> = AsgBuilderState<'i, FxBuildHasher, global::ProgIdentSize>; -pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> { +pub fn xmle(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> { let mut fs = VisitOnceFilesystem::new(); let mut depgraph = LinkerAsg::with_capacity(65536, 65536); let interner = DefaultInterner::new(); @@ -96,8 +97,6 @@ pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> { Err(e) => return Err(e.into()), }; - //println!("Sorted ({}): {:?}", sorted.len(), sorted); - output_xmle( &depgraph, &interner, @@ -110,6 +109,59 @@ pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> { Ok(()) } +pub fn graphml(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> { + let mut fs = VisitOnceFilesystem::new(); + let mut depgraph = LinkerAsg::with_capacity(65536, 65536); + let interner = DefaultInterner::new(); + + let _ = load_xmlo( + package_path, + &mut fs, + &mut depgraph, + &interner, + AsgBuilderState::new(), + )?; + + // if we move away from petgraph, we will need to abstract this away + let g = depgraph.into_inner(); + let graphml = + GraphMl::new(&g) + .pretty_print(true) + .export_node_weights(Box::new(|node| { + // eprintln!("{:?}", node); + + let (name, kind, generated) = match node { + Some(n) => { + let generated = match n.src() { + Some(src) => src.generated, + None => false, + }; + + ( + format!("{}", n), + n.kind().unwrap().as_ref(), + format!("{}", generated), + ) + } + None => ( + String::from("missing"), + "missing", + format!("{}", false), + ), + }; + + vec![ + ("label".into(), name.into()), + ("kind".into(), kind.into()), + ("generated".into(), generated.into()), + ] + })); + + fs::write(output, graphml.to_string())?; + + Ok(()) +} + fn load_xmlo<'a, 'i, I: Interner<'i>, P: AsRef<Path>>( path_str: P, fs: &mut VisitOnceFilesystem<FsCanonicalizer, FxBuildHasher>, diff --git a/tamer/tests/tameld.rs b/tamer/tests/tameld.rs index 30b2fea..1bbad58 100644 --- a/tamer/tests/tameld.rs +++ b/tamer/tests/tameld.rs @@ -68,6 +68,19 @@ fn link_input_file_does_not_exist() -> Result<(), Box<dyn std::error::Error>> { } #[test] +fn link_invalid_emit() -> Result<(), Box<dyn std::error::Error>> { + let mut cmd = Command::cargo_bin("tameld")?; + cmd.arg("foobar"); + cmd.arg("--emit").arg("notgood"); + cmd.arg("-o").arg("tests/data/test-output.xmle"); + cmd.assert() + .failure() + .stderr(predicate::str::contains("--emit notgood")); + + Ok(()) +} + +#[test] fn link_empty_input_file() -> Result<(), Box<dyn std::error::Error>> { let mut cmd = Command::cargo_bin("tameld")?; cmd.arg("tests/data/empty.xmlo"); |