Skip to content

Commit

Permalink
Implement ScopeGraphFormatter to dump dot representation of `ScopeG…
Browse files Browse the repository at this point in the history
…raph`
  • Loading branch information
Y-Nak committed Jul 16, 2023
1 parent f42dafa commit 190d823
Show file tree
Hide file tree
Showing 11 changed files with 498 additions and 48 deletions.
266 changes: 253 additions & 13 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/driver2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ common = { path = "../common2", package = "fe-common2" }
macros = { path = "../macros", package = "fe-macros" }
hir-analysis = { path = "../hir-analysis", package = "fe-hir-analysis" }
camino = "1.1.4"
clap = { version = "4.3", features = ["derive"] }
31 changes: 14 additions & 17 deletions crates/driver2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use common::{
InputDb, InputFile, InputIngot,
};
use hir::{
analysis_pass::AnalysisPassManager, diagnostics::DiagnosticVoucher, lower::map_file_to_mod,
HirDb, LowerHirDb, ParsingPass, SpannedHirDb,
analysis_pass::AnalysisPassManager, diagnostics::DiagnosticVoucher, hir_def::TopLevelMod,
lower::map_file_to_mod, HirDb, LowerHirDb, ParsingPass, SpannedHirDb,
};
use hir_analysis::{
name_resolution::{DefConflictAnalysisPass, ImportAnalysisPass, PathAnalysisPass},
Expand Down Expand Up @@ -43,21 +43,24 @@ pub struct DriverDataBase {

impl DriverDataBase {
// TODO: An temporary implementation for ui testing.
pub fn run_on_file(&mut self, file_path: &path::Path, source: &str) {
self.run_on_file_with_pass_manager(file_path, source, initialize_analysis_pass);
pub fn run_on_top_mod(&mut self, top_mod: TopLevelMod) {
self.run_on_file_with_pass_manager(top_mod, initialize_analysis_pass);
}

pub fn run_on_file_with_pass_manager<F>(
&mut self,
file_path: &path::Path,
source: &str,
pm_builder: F,
) where
pub fn run_on_file_with_pass_manager<F>(&mut self, top_mod: TopLevelMod, pm_builder: F)
where
F: FnOnce(&DriverDataBase) -> AnalysisPassManager<'_>,
{
self.diags.clear();
self.diags = {
let mut pass_manager = pm_builder(self);
pass_manager.run_on_module(top_mod)
};
}

pub fn top_mod_from_file(&mut self, file_path: &path::Path, source: &str) -> TopLevelMod {
let kind = IngotKind::StandAlone;

// We set the ingot version to 0.0.0 for stand-alone file.
let version = Version::new(0, 0, 0);
let root_file = file_path;
Expand All @@ -71,16 +74,10 @@ impl DriverDataBase {

let file_name = root_file.file_name().unwrap().to_str().unwrap();
let file = InputFile::new(self, ingot, file_name.into(), source.to_string());

ingot.set_root_file(self, file);
ingot.set_files(self, [file].into());

let top_mod = map_file_to_mod(self, file);

self.diags = {
let mut pass_manager = pm_builder(self);
pass_manager.run_on_module(top_mod)
};
map_file_to_mod(self, file)
}

/// Prints accumulated diagnostics to stderr.
Expand Down
37 changes: 34 additions & 3 deletions crates/driver2/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
use fe_driver2::DriverDataBase;

use clap::Parser;
use hir::hir_def::TopLevelMod;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// The file to compile.
#[arg()]
file_path: String,

/// Dump a graphviz dot file of the scope graph for the given file.
#[arg(long = "dump-scope-graph", default_value_t = false)]
dump_scope_graph: bool,
}

pub fn main() {
let arg = std::env::args().nth(1).unwrap();
let args = Args::parse();
let path = std::path::Path::new(&args.file_path);
if !path.exists() {
eprintln!("file '{}' does not exist", args.file_path);
std::process::exit(2);
}
let source = std::fs::read_to_string(&args.file_path).unwrap();

let mut db = DriverDataBase::default();
let source = std::fs::read_to_string(&arg).unwrap();
db.run_on_file(std::path::Path::new(&arg), &source);
let top_mod = db.top_mod_from_file(path, &source);
db.run_on_top_mod(top_mod);
db.emit_diags();

if args.dump_scope_graph {
println!("{}", dump_scope_graph(&db, top_mod));
}
}

fn dump_scope_graph(db: &DriverDataBase, top_mod: TopLevelMod) -> String {
let mut s = vec![];
top_mod.scope_graph(db).write_as_dot(db, &mut s).unwrap();
String::from_utf8(s).unwrap()
}
1 change: 1 addition & 0 deletions crates/hir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ camino = "1.1.4"
rustc-hash = "1.1.0"
smallvec = "1.10.0"
paste = "1.0"
dot2 = "1.0"

common = { path = "../common2", package = "fe-common2" }
parser = { path = "../parser2", package = "fe-parser2" }
Expand Down
10 changes: 5 additions & 5 deletions crates/hir/src/hir_def/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,19 @@ impl ItemKind {
pub fn kind_name(self) -> &'static str {
use ItemKind::*;
match self {
TopMod(_) => "module",
Mod(_) => "module",
Func(_) => "function",
TopMod(_) => "mod",
Mod(_) => "mod",
Func(_) => "fn",
Struct(_) => "struct",
Contract(_) => "contract",
Enum(_) => "enum",
TypeAlias(_) => "type alias",
TypeAlias(_) => "type",
Trait(_) => "trait",
Impl(_) => "impl",
ImplTrait(_) => "impl trait",
Const(_) => "const",
Use(_) => "use",
Body(_) => "expression body",
Body(_) => "body",
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/hir/src/hir_def/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub mod stmt;
pub mod types;
pub mod use_tree;

mod scope_graph_viz;

pub(crate) mod module_tree;

pub use attr::*;
Expand Down
12 changes: 8 additions & 4 deletions crates/hir/src/hir_def/scope_graph.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
///
use std::collections::BTreeSet;
use std::{collections::BTreeSet, io};

use rustc_hash::{FxHashMap, FxHashSet};

Expand All @@ -10,8 +9,8 @@ use crate::{
};

use super::{
Body, Enum, ExprId, Func, FuncParamLabel, IdentId, IngotId, ItemKind, TopLevelMod, Use,
Visibility,
scope_graph_viz::ScopeGraphFormatter, Body, Enum, ExprId, Func, FuncParamLabel, IdentId,
IngotId, ItemKind, TopLevelMod, Use, Visibility,
};

/// Represents a scope relation graph in a top-level module.
Expand Down Expand Up @@ -60,6 +59,11 @@ impl ScopeGraph {
self.scopes[&scope].edges.iter()
}

/// Write a scope graph as a dot file format to given `w`.
pub fn write_as_dot(&self, db: &dyn HirDb, w: &mut impl io::Write) -> io::Result<()> {
ScopeGraphFormatter::new(db, self).render(w)
}

pub fn scope_data(&self, scope: &ScopeId) -> &Scope {
&self.scopes[scope]
}
Expand Down
172 changes: 172 additions & 0 deletions crates/hir/src/hir_def/scope_graph_viz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
use std::{
collections::{hash_map::Entry, VecDeque},
io,
};

use cranelift_entity::{entity_impl, PrimaryMap};
use dot2::label::Text;
use rustc_hash::{FxHashMap, FxHashSet};

use crate::HirDb;

use super::scope_graph::{EdgeKind, ScopeGraph, ScopeId};

type NodeId = usize;

pub(super) struct ScopeGraphFormatter<'db> {
db: &'db dyn HirDb,
edges: PrimaryMap<EdgeId, Edge>,
nodes: Vec<ScopeId>,
}

impl<'db> ScopeGraphFormatter<'db> {
fn build_formatter(&mut self, s_graph: &ScopeGraph) {
let mut visited = FxHashSet::default();
let mut nodes_map = FxHashMap::default();

let mut worklist = VecDeque::new();
let root = s_graph.top_mod.scope();
worklist.push_back(root);
while let Some(scope) = worklist.pop_front() {
if !visited.insert(scope) {
continue;
}
let source = self.node_id(scope, &mut nodes_map);

for edge in s_graph.edges(scope) {
let target = self.node_id(edge.dest, &mut nodes_map);

self.edges.push(Edge {
kind: edge.kind,
target,
source,
});

if !visited.contains(&edge.dest) {
worklist.push_back(edge.dest);
}
}
}
}

fn node_id(&mut self, scope: ScopeId, nodes_map: &mut FxHashMap<ScopeId, usize>) -> NodeId {
match nodes_map.entry(scope) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
let id = self.nodes.len();
self.nodes.push(scope);
entry.insert(id);
id
}
}
}
}

impl<'db> ScopeGraphFormatter<'db> {
pub(super) fn new(db: &'db dyn HirDb, s_graph: &ScopeGraph) -> Self {
let nodes = Vec::new();
let edges = PrimaryMap::new();
let mut formatter = Self { db, edges, nodes };

formatter.build_formatter(s_graph);
formatter
}

pub(super) fn render(&self, w: &mut impl io::Write) -> io::Result<()> {
dot2::render(self, w).map_err(|err| match err {
dot2::Error::Io(err) => err,
dot2::Error::InvalidId => unreachable!(),
})
}
}

impl<'db, 'a> dot2::Labeller<'a> for ScopeGraphFormatter<'db> {
type Node = NodeId;
type Edge = EdgeId;
type Subgraph = ();

fn graph_id(&'a self) -> dot2::Result<dot2::Id<'a>> {
dot2::Id::new("example1")
}

fn node_id(&'a self, n: &Self::Node) -> dot2::Result<dot2::Id<'a>> {
dot2::Id::new(format!("N{n}"))
}

fn node_label<'b>(&'a self, node: &Self::Node) -> dot2::Result<Text<'db>> {
let label = match &self.nodes[*node] {
ScopeId::Item(item) => format!(
"{} {}",
item.kind_name(),
item.name(self.db).map_or("", |name| name.data(self.db))
),

ScopeId::Block(body, expr) => {
let idx = body.block_order(self.db)[expr];
format!("{{block{}}}", idx)
}

scope => scope
.name(self.db)
.map_or(String::new(), |name| name.data(self.db).to_string()),
};
Ok(Text::LabelStr(label.into()))
}

fn edge_label(&'a self, e: &Self::Edge) -> Text<'a> {
let edge = &self.edges[*e];

let label = match edge.kind {
EdgeKind::Lex(_) => "lex",
EdgeKind::Mod(_) => "mod",
EdgeKind::Type(_) => "type",
EdgeKind::Trait(_) => "trait",
EdgeKind::GenericParam(_) => "generic_param",
EdgeKind::Value(_) => "value",
EdgeKind::Field(_) => "field",
EdgeKind::Variant(_) => "variant",
EdgeKind::Super(_) => "super",
EdgeKind::Ingot(_) => "ingot",
EdgeKind::Self_(_) => "self",
EdgeKind::SelfTy(_) => "self_ty",
EdgeKind::Anon(_) => "anon",
};
Text::LabelStr(label.into())
}

fn node_shape(&self, _n: &Self::Node) -> Option<Text<'db>> {
Some(Text::LabelStr("box".into()))
}
}

impl<'db, 'a> dot2::GraphWalk<'a> for ScopeGraphFormatter<'db> {
type Node = NodeId;
type Edge = EdgeId;
type Subgraph = ();

fn nodes(&'a self) -> dot2::Nodes<'a, Self::Node> {
(0..self.nodes.len()).collect()
}

fn edges(&'a self) -> dot2::Edges<'a, Self::Edge> {
self.edges.keys().collect()
}

fn source(&self, e: &Self::Edge) -> Self::Node {
self.edges[*e].source
}

fn target(&self, e: &Self::Edge) -> Self::Node {
self.edges[*e].target
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(super) struct EdgeId(u32);
entity_impl!(EdgeId);

struct Edge {
kind: EdgeKind,
target: NodeId,
source: NodeId,
}
8 changes: 4 additions & 4 deletions crates/mir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ license = "Apache-2.0"
repository = "https://github.com/ethereum/fe"

[dependencies]
fe-common = { path = "../common", version = "^0.23.0"}
fe-parser = { path = "../parser", version = "^0.23.0"}
fe-analyzer = { path = "../analyzer", version = "^0.23.0"}
fe-common = { path = "../common", version = "^0.23.0" }
fe-parser = { path = "../parser", version = "^0.23.0" }
fe-analyzer = { path = "../analyzer", version = "^0.23.0" }
salsa = "0.16.1"
smol_str = "0.1.21"
num-bigint = "0.4.3"
num-traits = "0.2.14"
num-integer = "0.1.45"
id-arena = "2.2.1"
fxhash = "0.2.1"
dot2 = "0.1.0"
dot2 = "1.0.0"
indexmap = "1.6.2"

[dev-dependencies]
Expand Down
6 changes: 4 additions & 2 deletions crates/uitest/tests/name_resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use fe_compiler_test_utils::snap_test;
fn run_name_resolution(fixture: Fixture<&str>) {
let mut driver = DriverDataBase::default();
let path = Path::new(fixture.path());
driver.run_on_file(path, fixture.content());
let top_mod = driver.top_mod_from_file(path, fixture.content());
driver.run_on_top_mod(top_mod);
let diags = driver.format_diags();
snap_test!(diags, fixture.path());
}
Expand All @@ -32,6 +33,7 @@ mod wasm {
fn run_name_resolution(fixture: Fixture<&str>) {
let mut driver = DriverDataBase::default();
let path = Path::new(fixture.path());
driver.run_on_file(path, fixture.content());
let top_mod = driver.top_mod_from_file(path, fixture.content());
driver.run_on_top_mod(top_mod);
}
}

0 comments on commit 190d823

Please sign in to comment.