Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
path: root/tamer/src
diff options
context:
space:
mode:
authorJoseph Frazer <joseph.frazer@ryansg.com>2020-04-30 14:33:10 -0400
committerJoseph Frazer <joseph.frazer@ryansg.com>2020-05-13 08:04:48 -0400
commit43d00a8268d69bee00e0e2d9ebffd33b66d3062d (patch)
treed772323a848b00d83922ab1185233a2d13251674 /tamer/src
parent18d87a6b00a9e2e4e9d859320a0ca7a38c5b24a0 (diff)
downloadtame-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/src')
-rw-r--r--tamer/src/bin/tameld.rs104
-rw-r--r--tamer/src/ir/asg/base.rs5
-rw-r--r--tamer/src/ir/asg/ident.rs63
-rw-r--r--tamer/src/ir/asg/object.rs9
-rw-r--r--tamer/src/ld/poc.rs58
5 files changed, 205 insertions, 34 deletions
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>,