tamer: ir::asg::SortableAsg: Move into ld::xmle::lower
This has always been a lowering operation, but it was not phrased in terms of it, which made the process a bit more confusing to understand. The implementation hasn't changed, but this is an incremental refactoring and so exposes BaseAsg and its `graph` field temporarily. DEV-10859main
parent
81ec65742a
commit
df328da71f
|
@ -30,9 +30,7 @@ use test::Bencher;
|
|||
|
||||
mod base {
|
||||
use super::*;
|
||||
use tamer::ir::asg::{
|
||||
Asg, DataType, DefaultAsg, IdentKind, IdentObject, SortableAsg, Source,
|
||||
};
|
||||
use tamer::ir::asg::{Asg, DefaultAsg, IdentKind, IdentObject, Source};
|
||||
use tamer::sym::{GlobalSymbolIntern, SymbolId};
|
||||
|
||||
type Sut = DefaultAsg<IdentObject>;
|
||||
|
@ -307,71 +305,6 @@ mod base {
|
|||
.for_each(drop);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn sort_1_with_1_000_existing_supernode(bench: &mut Bencher) {
|
||||
let mut sut = Sut::new();
|
||||
let xs = interned_n(1_000);
|
||||
|
||||
let orefs = xs
|
||||
.iter()
|
||||
.map(|sym| {
|
||||
sut.declare(
|
||||
*sym,
|
||||
IdentKind::Rate(DataType::Integer),
|
||||
Source::default(),
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let root = orefs[0];
|
||||
|
||||
// All edges from a single node.
|
||||
orefs.iter().skip(1).for_each(|to| {
|
||||
sut.add_dep(root, *to);
|
||||
});
|
||||
|
||||
bench.iter(|| {
|
||||
drop(sut.sort(&[root]));
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn sort_1_with_1_000_existing_one_edge_per_node_one_path(
|
||||
bench: &mut Bencher,
|
||||
) {
|
||||
let mut sut = Sut::new();
|
||||
let xs = interned_n(1_000);
|
||||
|
||||
let orefs = xs
|
||||
.iter()
|
||||
.map(|sym| {
|
||||
sut.declare(
|
||||
*sym,
|
||||
IdentKind::Rate(DataType::Integer),
|
||||
Source::default(),
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Note that there's no `cycle` call on the iterator, like the
|
||||
// above tests, to make sure we don't create a cycle on the
|
||||
// graph.
|
||||
orefs
|
||||
.iter()
|
||||
.zip(orefs.iter().skip(1))
|
||||
.for_each(|(from, to)| {
|
||||
sut.add_dep(*from, *to);
|
||||
});
|
||||
|
||||
let root = orefs[0];
|
||||
|
||||
bench.iter(|| {
|
||||
drop(sut.sort(&[root]));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mod object {
|
||||
|
|
|
@ -19,18 +19,13 @@
|
|||
|
||||
//! Base concrete [`Asg`] implementation.
|
||||
|
||||
use super::graph::{
|
||||
Asg, AsgEdge, AsgResult, IndexType, Node, ObjectRef, SortableAsg,
|
||||
SortableAsgError, SortableAsgResult,
|
||||
};
|
||||
use super::graph::{Asg, AsgEdge, AsgResult, IndexType, Node, ObjectRef};
|
||||
use super::ident::IdentKind;
|
||||
use super::object::{
|
||||
FragmentText, IdentObjectData, IdentObjectState, Source, TransitionResult,
|
||||
};
|
||||
use crate::ld::xmle::Sections;
|
||||
use crate::sym::{GlobalSymbolResolve, SymbolId};
|
||||
use crate::sym::SymbolId;
|
||||
use petgraph::graph::{DiGraph, Graph, NodeIndex};
|
||||
use petgraph::visit::DfsPostOrder;
|
||||
|
||||
/// Concrete ASG.
|
||||
///
|
||||
|
@ -46,8 +41,9 @@ pub struct BaseAsg<O, Ix>
|
|||
where
|
||||
Ix: IndexType,
|
||||
{
|
||||
// TODO: private; see `ld::xmle::lower`.
|
||||
/// Directed graph on which objects are stored.
|
||||
graph: DiGraph<Node<O>, AsgEdge, Ix>,
|
||||
pub graph: DiGraph<Node<O>, AsgEdge, Ix>,
|
||||
|
||||
/// Map of [`SymbolId`][crate::sym::SymbolId] to node indexes.
|
||||
///
|
||||
|
@ -274,126 +270,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<O, Ix> SortableAsg<O, Ix> for BaseAsg<O, Ix>
|
||||
where
|
||||
Ix: IndexType,
|
||||
O: IdentObjectData + IdentObjectState<O>,
|
||||
{
|
||||
fn sort<'i>(
|
||||
&'i self,
|
||||
roots: &[ObjectRef<Ix>],
|
||||
) -> SortableAsgResult<Sections<'i, O>, Ix> {
|
||||
let mut deps = Sections::new();
|
||||
|
||||
check_cycles(self)?;
|
||||
|
||||
// This is technically a topological sort, but functions have cycles.
|
||||
let mut dfs = DfsPostOrder::empty(&self.graph);
|
||||
|
||||
for index in roots {
|
||||
dfs.stack.push((*index).into());
|
||||
}
|
||||
|
||||
while let Some(index) = dfs.next(&self.graph) {
|
||||
let ident = self.get(index).expect("missing node").resolved()?;
|
||||
|
||||
match ident.kind() {
|
||||
Some(kind) => match kind {
|
||||
IdentKind::Meta
|
||||
| IdentKind::Worksheet
|
||||
| IdentKind::Param(_, _)
|
||||
| IdentKind::Type(_)
|
||||
| IdentKind::Func(_, _)
|
||||
| IdentKind::Const(_, _) => deps.st.push_body(ident),
|
||||
IdentKind::MapHead
|
||||
| IdentKind::Map
|
||||
| IdentKind::MapTail => deps.map.push_body(ident),
|
||||
IdentKind::RetMapHead
|
||||
| IdentKind::RetMap
|
||||
| IdentKind::RetMapTail => deps.retmap.push_body(ident),
|
||||
_ => deps.rater.push_body(ident),
|
||||
},
|
||||
None => {
|
||||
return Err(SortableAsgError::MissingObjectKind(
|
||||
ident
|
||||
.name()
|
||||
.map(|name| format!("{}", name.lookup_str()))
|
||||
.unwrap_or("<unknown>".into())
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(deps)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check graph for cycles
|
||||
///
|
||||
/// We want to catch any cycles before we start using the graph.
|
||||
/// Unfortunately, we need to allow cycles for our [`IdentKind::Func`]
|
||||
/// so we cannot use the typical algorithms in a straightforward manner.
|
||||
///
|
||||
/// We loop through all SCCs and check that they are not all functions. If
|
||||
/// they are, we ignore the cycle, otherwise we will return an error.
|
||||
fn check_cycles<O, Ix>(asg: &BaseAsg<O, Ix>) -> SortableAsgResult<(), Ix>
|
||||
where
|
||||
Ix: IndexType,
|
||||
O: IdentObjectData + IdentObjectState<O>,
|
||||
{
|
||||
// While `tarjan_scc` does do a topological sort, it does not suit our
|
||||
// needs because we need to filter out some allowed cycles. It would
|
||||
// still be possible to use this, but we also need to only check nodes
|
||||
// that are attached to our "roots". We are doing our own sort and as of
|
||||
// the initial writing, this does not have a significant performance
|
||||
// impact.
|
||||
let sccs = petgraph::algo::tarjan_scc(&asg.graph);
|
||||
|
||||
let cycles: Vec<_> = sccs
|
||||
.into_iter()
|
||||
.filter_map(|scc| {
|
||||
// For single-node SCCs, we just need to make sure they are
|
||||
// not neighbors with themselves.
|
||||
if scc.len() == 1
|
||||
&& !asg.graph.neighbors(scc[0]).any(|nx| nx == scc[0])
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let is_all_funcs = scc.iter().all(|nx| {
|
||||
let ident = Asg::get(asg, *nx).expect("missing node");
|
||||
matches!(ident.kind(), Some(IdentKind::Func(_, _)))
|
||||
});
|
||||
|
||||
if is_all_funcs {
|
||||
None
|
||||
} else {
|
||||
let cycles =
|
||||
scc.iter().map(|nx| ObjectRef::from(*nx)).collect();
|
||||
Some(cycles)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if cycles.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SortableAsgError::Cycles(cycles))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::graph::AsgError;
|
||||
use super::*;
|
||||
use crate::ir::asg::{
|
||||
DataType, Dim, IdentObject, TransitionError, TransitionResult,
|
||||
UnresolvedError,
|
||||
Dim, IdentObject, TransitionError, TransitionResult, UnresolvedError,
|
||||
};
|
||||
use crate::ir::legacyir::SymDtype;
|
||||
use crate::sym::GlobalSymbolIntern;
|
||||
use crate::sym::SymbolId;
|
||||
use crate::sym::{GlobalSymbolIntern, SymbolId};
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
|
@ -855,707 +739,4 @@ mod test {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! assert_section_sym {
|
||||
( $iterable:expr, $s:ident ) => {{
|
||||
let mut pos = 0;
|
||||
|
||||
for obj in $iterable.iter() {
|
||||
let sym = obj.name().expect("missing object");
|
||||
assert_eq!($s.get(pos).map(|s| *s), Some(sym));
|
||||
|
||||
pos = pos + 1;
|
||||
}
|
||||
|
||||
assert_eq!(pos, $s.len());
|
||||
};};
|
||||
}
|
||||
|
||||
macro_rules! add_syms {
|
||||
($sut:ident, $base:expr, {$($dest:ident <- $name:ident: $kind:expr,)*}) => {
|
||||
$(
|
||||
let sym = stringify!($name).intern();
|
||||
|
||||
$sut.declare(sym, $kind, Source::default()).unwrap();
|
||||
let (_, _) = $sut.add_dep_lookup($base, sym);
|
||||
|
||||
$dest.push(sym);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort() -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let mut st = vec![];
|
||||
let mut map = vec![];
|
||||
let mut retmap = vec![];
|
||||
|
||||
let base = "sym1".intern();
|
||||
let base_node = sut
|
||||
.declare(base, IdentKind::Map, Source::default())
|
||||
.unwrap();
|
||||
|
||||
add_syms!(sut, base, {
|
||||
st <- meta1: IdentKind::Meta,
|
||||
st <- work1: IdentKind::Worksheet,
|
||||
st <- const1: IdentKind::Const(Dim::from_u8(0), DataType::Float),
|
||||
map <- map1: IdentKind::MapHead,
|
||||
map <- map2: IdentKind::Map,
|
||||
map <- map3: IdentKind::MapTail,
|
||||
retmap <- retmap1: IdentKind::RetMapHead,
|
||||
retmap <- retmap2: IdentKind::RetMap,
|
||||
retmap <- retmap3: IdentKind::RetMapTail,
|
||||
retmap <- retmap4: IdentKind::RetMapTail,
|
||||
retmap <- retmap5: IdentKind::RetMap,
|
||||
map <- map4: IdentKind::MapHead,
|
||||
map <- map5: IdentKind::Map,
|
||||
st <- meta2: IdentKind::Meta,
|
||||
st <- work2: IdentKind::Worksheet,
|
||||
map <- map6: IdentKind::MapTail,
|
||||
retmap <- retmap6: IdentKind::RetMapHead,
|
||||
st <- const2: IdentKind::Const(Dim::from_u8(2), DataType::Float),
|
||||
});
|
||||
|
||||
map.push(base);
|
||||
|
||||
let sections = sut.sort(&vec![base_node])?;
|
||||
|
||||
assert_section_sym!(sections.st, st);
|
||||
assert_section_sym!(sections.map, map);
|
||||
assert_section_sym!(sections.retmap, retmap);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_missing_node() -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym = "sym".intern();
|
||||
let dep = "dep".intern();
|
||||
|
||||
let sym_node = sut
|
||||
.declare(
|
||||
sym,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||||
.unwrap();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||||
|
||||
match sut.sort(&vec![sym_node]) {
|
||||
Ok(_) => panic!("Unexpected success - dependency is not in graph"),
|
||||
Err(SortableAsgError::MissingObjectKind(_)) => (),
|
||||
_ => {
|
||||
panic!("Incorrect error result when dependency is not in graph")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_no_roots() -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym = "sym".intern();
|
||||
let dep = "dep".intern();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||||
|
||||
let sections = sut.sort(&vec![])?;
|
||||
|
||||
assert_eq!(Sections::new(), sections);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_simple_cycle() -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym = "sym".intern();
|
||||
let dep = "dep".intern();
|
||||
|
||||
let sym_node = sut
|
||||
.declare(
|
||||
sym,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dep_node = sut
|
||||
.declare(
|
||||
dep,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||||
.unwrap();
|
||||
sut.set_fragment(dep_node, FragmentText::from("bar"))
|
||||
.unwrap();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||||
let (_, _) = sut.add_dep_lookup(dep, sym);
|
||||
|
||||
let result = sut.sort(&vec![sym_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u16>>> =
|
||||
vec![vec![dep_node.into(), sym_node.into()]];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_two_simple_cycles() -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym = "sym".intern();
|
||||
let sym2 = "sym2".intern();
|
||||
let dep = "dep".intern();
|
||||
let dep2 = "dep2".intern();
|
||||
|
||||
let sym_node = sut
|
||||
.declare(
|
||||
sym,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sym2_node = sut
|
||||
.declare(
|
||||
sym2,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dep_node = sut
|
||||
.declare(
|
||||
dep,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dep2_node = sut
|
||||
.declare(
|
||||
dep2,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||||
.unwrap();
|
||||
sut.set_fragment(sym2_node, FragmentText::from("bar"))
|
||||
.unwrap();
|
||||
sut.set_fragment(dep_node, FragmentText::from("baz"))
|
||||
.unwrap();
|
||||
sut.set_fragment(dep2_node, FragmentText::from("huh"))
|
||||
.unwrap();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||||
let (_, _) = sut.add_dep_lookup(dep, sym);
|
||||
let (_, _) = sut.add_dep_lookup(sym2, dep2);
|
||||
let (_, _) = sut.add_dep_lookup(dep2, sym2);
|
||||
|
||||
let result = sut.sort(&vec![sym_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u16>>> = vec![
|
||||
vec![dep_node.into(), sym_node.into()],
|
||||
vec![dep2_node.into(), sym2_node.into()],
|
||||
];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_no_cycle_with_edge_to_same_node() -> SortableAsgResult<(), u16>
|
||||
{
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym = "sym".intern();
|
||||
let dep = "dep".intern();
|
||||
|
||||
let sym_node = sut
|
||||
.declare(
|
||||
sym,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dep_node = sut
|
||||
.declare(
|
||||
dep,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||||
.unwrap();
|
||||
sut.set_fragment(dep_node, FragmentText::from("bar"))
|
||||
.unwrap();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||||
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||||
|
||||
let result = sut.sort(&vec![sym_node]);
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_cycle_with_a_few_steps() -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym1 = "sym1".intern();
|
||||
let sym2 = "sym2".intern();
|
||||
let sym3 = "sym3".intern();
|
||||
|
||||
let sym1_node = sut
|
||||
.declare(
|
||||
sym1,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sym2_node = sut
|
||||
.declare(
|
||||
sym2,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sym3_node = sut
|
||||
.declare(
|
||||
sym3,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sut.set_fragment(sym1_node, FragmentText::from("foo"))
|
||||
.unwrap();
|
||||
sut.set_fragment(sym2_node, FragmentText::from("bar"))
|
||||
.unwrap();
|
||||
sut.set_fragment(sym3_node, FragmentText::from("baz"))
|
||||
.unwrap();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym1, sym2);
|
||||
let (_, _) = sut.add_dep_lookup(sym2, sym3);
|
||||
let (_, _) = sut.add_dep_lookup(sym3, sym1);
|
||||
|
||||
let result = sut.sort(&vec![sym1_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u16>>> =
|
||||
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_cyclic_function_with_non_function_with_a_few_steps(
|
||||
) -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym1 = "sym1".intern();
|
||||
let sym2 = "sym2".intern();
|
||||
let sym3 = "sym3".intern();
|
||||
|
||||
let sym1_node = sut
|
||||
.declare(
|
||||
sym1,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sym2_node = sut
|
||||
.declare(
|
||||
sym2,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sym3_node = sut
|
||||
.declare(
|
||||
sym3,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sut.set_fragment(sym1_node, FragmentText::from("foo"))
|
||||
.unwrap();
|
||||
sut.set_fragment(sym2_node, FragmentText::from("bar"))
|
||||
.unwrap();
|
||||
sut.set_fragment(sym3_node, FragmentText::from("baz"))
|
||||
.unwrap();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym1, sym2);
|
||||
let (_, _) = sut.add_dep_lookup(sym2, sym3);
|
||||
let (_, _) = sut.add_dep_lookup(sym3, sym1);
|
||||
|
||||
let result = sut.sort(&vec![sym1_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u16>>> =
|
||||
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_cyclic_bookended_by_functions() -> SortableAsgResult<(), u16>
|
||||
{
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym1 = "sym1".intern();
|
||||
let sym2 = "sym2".intern();
|
||||
let sym3 = "sym3".intern();
|
||||
|
||||
let sym1_node = sut
|
||||
.declare(
|
||||
sym1,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sym2_node = sut
|
||||
.declare(
|
||||
sym2,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sym3_node = sut
|
||||
.declare(
|
||||
sym3,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sut.set_fragment(sym1_node, FragmentText::from("foo"))
|
||||
.unwrap();
|
||||
sut.set_fragment(sym2_node, FragmentText::from("bar"))
|
||||
.unwrap();
|
||||
sut.set_fragment(sym3_node, FragmentText::from("baz"))
|
||||
.unwrap();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym1, sym2);
|
||||
let (_, _) = sut.add_dep_lookup(sym2, sym3);
|
||||
let (_, _) = sut.add_dep_lookup(sym3, sym1);
|
||||
|
||||
let result = sut.sort(&vec![sym1_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u16>>> =
|
||||
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_cyclic_function_ignored() -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym = "sym".intern();
|
||||
let dep = "dep".intern();
|
||||
|
||||
let sym_node = sut
|
||||
.declare(
|
||||
sym,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dep_node = sut
|
||||
.declare(
|
||||
dep,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||||
.unwrap();
|
||||
sut.set_fragment(dep_node, FragmentText::from("bar"))
|
||||
.unwrap();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||||
let (_, _) = sut.add_dep_lookup(dep, sym);
|
||||
|
||||
let result = sut.sort(&vec![sym_node]);
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_cyclic_function_is_bookended() -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym1 = "sym1".intern();
|
||||
let sym2 = "sym2".intern();
|
||||
let sym3 = "sym3".intern();
|
||||
|
||||
let sym1_node = sut
|
||||
.declare(
|
||||
sym1,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sym2_node = sut
|
||||
.declare(
|
||||
sym2,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sym3_node = sut
|
||||
.declare(
|
||||
sym3,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sut.set_fragment(sym1_node, FragmentText::from("foo"))
|
||||
.unwrap();
|
||||
sut.set_fragment(sym2_node, FragmentText::from("bar"))
|
||||
.unwrap();
|
||||
sut.set_fragment(sym3_node, FragmentText::from("baz"))
|
||||
.unwrap();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym1, sym2);
|
||||
let (_, _) = sut.add_dep_lookup(sym2, sym3);
|
||||
let (_, _) = sut.add_dep_lookup(sym3, sym1);
|
||||
|
||||
let result = sut.sort(&vec![sym1_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u16>>> =
|
||||
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_ignore_non_linked() -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym = "sym".intern();
|
||||
let dep = "dep".intern();
|
||||
let ignored = "ignored".intern();
|
||||
|
||||
let sym_node = sut
|
||||
.declare(
|
||||
sym,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dep_node = sut
|
||||
.declare(
|
||||
dep,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ignored_node = sut
|
||||
.declare(
|
||||
ignored,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||||
.unwrap();
|
||||
sut.set_fragment(dep_node, FragmentText::from("bar"))
|
||||
.unwrap();
|
||||
sut.set_fragment(ignored_node, FragmentText::from("baz"))
|
||||
.unwrap();
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||||
let (_, _) = sut.add_dep_lookup(ignored, sym);
|
||||
|
||||
let result = sut.sort(&vec![sym_node]);
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A graph containing unresolved objects cannot be sorted.
|
||||
#[test]
|
||||
fn graph_sort_fail_unresolved() -> SortableAsgResult<(), u16> {
|
||||
let mut sut = Sut::new();
|
||||
|
||||
let sym = "unresolved".intern();
|
||||
let node = sut
|
||||
.declare(sym, IdentKind::Meta, Default::default())
|
||||
.unwrap();
|
||||
let ident = sut.get(node).unwrap();
|
||||
|
||||
let expected = UnresolvedError::Missing { name: sym };
|
||||
|
||||
// Cause resolved() to fail.
|
||||
ident.fail_resolved.replace(Some(expected.clone()));
|
||||
|
||||
let result = sut
|
||||
.sort(&vec![node])
|
||||
.expect_err("expected sort failure due to unresolved identifier");
|
||||
|
||||
match result {
|
||||
SortableAsgError::UnresolvedObject(err) => {
|
||||
assert_eq!(expected, err);
|
||||
}
|
||||
_ => panic!("expected SortableAsgError::Unresolved: {:?}", result),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,7 @@
|
|||
//! Abstract graph as the basis for concrete ASGs.
|
||||
|
||||
use super::ident::IdentKind;
|
||||
use super::object::{
|
||||
FragmentText, IdentObjectData, IdentObjectState, Source, TransitionError,
|
||||
UnresolvedError,
|
||||
};
|
||||
use crate::ld::xmle::Sections;
|
||||
use super::object::{FragmentText, IdentObjectState, Source, TransitionError};
|
||||
use crate::sym::SymbolId;
|
||||
use petgraph::graph::NodeIndex;
|
||||
use std::fmt::Debug;
|
||||
|
@ -178,38 +174,12 @@ where
|
|||
) -> (ObjectRef<Ix>, ObjectRef<Ix>);
|
||||
}
|
||||
|
||||
/// Sort a graph into different [`Sections`]
|
||||
///
|
||||
/// Allow a graph to be partitioned into different [`Sections`] that can be
|
||||
/// used as an `Intermediate Representation`.
|
||||
pub trait SortableAsg<O, Ix>
|
||||
where
|
||||
O: IdentObjectData,
|
||||
Ix: IndexType,
|
||||
{
|
||||
/// Sort graph into [`Sections`].
|
||||
///
|
||||
/// Sorting will fail if the graph contains unresolved objects,
|
||||
/// or identifiers whose kind cannot be determined
|
||||
/// (see [`UnresolvedError`]).
|
||||
fn sort<'i>(
|
||||
&'i self,
|
||||
roots: &[ObjectRef<Ix>],
|
||||
) -> SortableAsgResult<Sections<'i, O>, Ix>;
|
||||
}
|
||||
|
||||
/// A [`Result`] with a hard-coded [`AsgError`] error type.
|
||||
///
|
||||
/// This is the result of every [`Asg`] operation that could potentially
|
||||
/// fail in error.
|
||||
pub type AsgResult<T> = Result<T, AsgError>;
|
||||
|
||||
/// A [`Result`] with a hard-coded [`SortableAsgError`] error type.
|
||||
///
|
||||
/// This is the result of every [`SortableAsg`] operation that could
|
||||
/// potentially fail in error.
|
||||
pub type SortableAsgResult<T, Ix> = Result<T, SortableAsgError<Ix>>;
|
||||
|
||||
/// Reference to an [object][super::object] stored within the [`Asg`].
|
||||
///
|
||||
/// IdentObject references are integer offsets,
|
||||
|
@ -285,66 +255,6 @@ impl From<TransitionError> for AsgError {
|
|||
}
|
||||
}
|
||||
|
||||
/// Error during graph sorting.
|
||||
///
|
||||
/// These errors reflect barriers to meaningfully understanding the
|
||||
/// properties of the data in the graph with respect to sorting.
|
||||
/// It does not represent bad underlying data that does not affect the
|
||||
/// sorting process.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum SortableAsgError<Ix: IndexType> {
|
||||
/// An unresolved object was encountered during sorting.
|
||||
///
|
||||
/// An unresolved object means that the graph has an incomplete picture
|
||||
/// of the program,
|
||||
/// and so sorting cannot be reliably performed.
|
||||
/// Since all objects are supposed to be resolved prior to sorting,
|
||||
/// this represents either a problem with the program being compiled
|
||||
/// or a bug in the compiler itself.
|
||||
UnresolvedObject(UnresolvedError),
|
||||
|
||||
/// The kind of an object encountered during sorting could not be
|
||||
/// determined.
|
||||
///
|
||||
/// Sorting uses the object kind to place objects into their appropriate
|
||||
/// sections.
|
||||
/// It should never be the case that a resolved object has no kind,
|
||||
/// so this likely represents a compiler bug.
|
||||
MissingObjectKind(String),
|
||||
|
||||
/// The graph has a cyclic dependency.
|
||||
Cycles(Vec<Vec<ObjectRef<Ix>>>),
|
||||
}
|
||||
|
||||
impl<Ix: IndexType> From<UnresolvedError> for SortableAsgError<Ix> {
|
||||
fn from(err: UnresolvedError) -> Self {
|
||||
Self::UnresolvedObject(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ix: IndexType> std::fmt::Display for SortableAsgError<Ix> {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UnresolvedObject(err) => std::fmt::Display::fmt(err, fmt),
|
||||
Self::MissingObjectKind(name) => write!(
|
||||
fmt,
|
||||
"internal error: missing object kind for object `{}` (this may be a compiler bug!)",
|
||||
name,
|
||||
),
|
||||
Self::Cycles(_) => write!(fmt, "cyclic dependencies"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ix: IndexType> std::error::Error for SortableAsgError<Ix> {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::UnresolvedObject(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
|
@ -195,10 +195,10 @@ mod graph;
|
|||
mod ident;
|
||||
mod object;
|
||||
|
||||
pub use graph::{
|
||||
Asg, AsgError, AsgResult, IndexType, ObjectRef, SortableAsg,
|
||||
SortableAsgError,
|
||||
};
|
||||
// TODO: Stop exposing this. See `ld::xmle::lower`.
|
||||
pub use base::BaseAsg;
|
||||
|
||||
pub use graph::{Asg, AsgError, AsgResult, IndexType, ObjectRef};
|
||||
pub use ident::{DataType, Dim, IdentKind, IdentKindError};
|
||||
pub use object::{
|
||||
FragmentText, IdentObject, IdentObjectData, IdentObjectState, Source,
|
||||
|
|
|
@ -20,15 +20,16 @@
|
|||
//! **This contains the remaining portions of the proof-of-concept linker.**
|
||||
//! It is feature-complete and just needs final refactoring.
|
||||
|
||||
use super::xmle::{xir::lower_iter, Sections};
|
||||
use super::xmle::{
|
||||
lower::{sort, SortError},
|
||||
xir::lower_iter,
|
||||
Sections,
|
||||
};
|
||||
use crate::fs::{
|
||||
Filesystem, FsCanonicalizer, PathFile, VisitOnceFile, VisitOnceFilesystem,
|
||||
};
|
||||
use crate::global;
|
||||
use crate::ir::asg::{
|
||||
Asg, DefaultAsg, IdentObject, IdentObjectData, SortableAsg,
|
||||
SortableAsgError,
|
||||
};
|
||||
use crate::ir::asg::{Asg, DefaultAsg, IdentObject, IdentObjectData};
|
||||
use crate::ir::xir::writer::XmlWriter;
|
||||
use crate::obj::xmlo::{AsgBuilder, AsgBuilderState, XmloReader};
|
||||
use crate::sym::SymbolId;
|
||||
|
@ -71,9 +72,9 @@ pub fn xmle(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
|
|||
.filter_map(|sym| depgraph.lookup(sym)),
|
||||
);
|
||||
|
||||
let mut sorted = match depgraph.sort(&roots) {
|
||||
let mut sorted = match sort(&depgraph, &roots) {
|
||||
Ok(sections) => sections,
|
||||
Err(SortableAsgError::Cycles(cycles)) => {
|
||||
Err(SortError::Cycles(cycles)) => {
|
||||
let msg: Vec<String> = cycles
|
||||
.into_iter()
|
||||
.map(|cycle| {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -70,6 +70,8 @@
|
|||
//! ```
|
||||
|
||||
mod section;
|
||||
|
||||
pub mod lower;
|
||||
pub mod xir;
|
||||
|
||||
pub use section::{Section, SectionIter, Sections, SectionsIter};
|
||||
|
|
|
@ -19,11 +19,9 @@
|
|||
|
||||
//! Sections of a linked [`xmle`](super) object file.
|
||||
//!
|
||||
//! These sections are the result of [`SortableAsg::sort`],
|
||||
//! These sections are the result of [`sort`](super::lower::sort),
|
||||
//! which places the relocatable object code fragments in the order
|
||||
//! necessary for execution.
|
||||
//!
|
||||
//! [`SortableAsg::sort`]: crate::ir::asg::SortableAsg::sort
|
||||
|
||||
use crate::ir::asg::IdentObjectData;
|
||||
use crate::sym::SymbolId;
|
||||
|
|
Loading…
Reference in New Issue