// ASG lowering into xmle sections // // Copyright (C) 2014-2021 Ryan Specialty Group, LLC. // // This file is part of TAME. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! Lowering of the [ASG](crate::ir::asg) into `xmle` [`Sections`]. //! //! See the [parent module](super) for more information. use super::Sections; use crate::{ ir::asg::{ Asg, BaseAsg, IdentKind, IdentObjectData, IdentObjectState, IndexType, ObjectRef, UnresolvedError, }, sym::GlobalSymbolResolve, }; use petgraph::visit::DfsPostOrder; // Result of [`sort`]. pub type SortResult = Result>; /// Lower ASG into [`Sections`] by sorting relocatable text fragments. /// /// This performs the equivalent of a topological sort, /// although function cycles are permitted. /// The actual operation performed is a post-order depth-first traversal. pub fn sort<'a, O, Ix>( asg: &'a BaseAsg, roots: &[ObjectRef], ) -> SortResult, Ix> where Ix: IndexType, O: IdentObjectData + IdentObjectState, { let mut deps = Sections::new(); // TODO: we should check for cycles as we sort (as the POC did). check_cycles(asg)?; // This is technically a topological sort, but functions have cycles. let mut dfs = DfsPostOrder::empty(&asg.graph); for index in roots { dfs.stack.push((*index).into()); } while let Some(index) = dfs.next(&asg.graph) { let ident = asg.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(SortError::MissingObjectKind( ident .name() .map(|name| format!("{}", name.lookup_str())) .unwrap_or("".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(asg: &BaseAsg) -> SortResult<(), Ix> where Ix: IndexType, O: IdentObjectData + IdentObjectState, { // 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(SortError::Cycles(cycles)) } } /// 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 SortError { /// 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>>), } impl From for SortError { fn from(err: UnresolvedError) -> Self { Self::UnresolvedObject(err) } } impl std::fmt::Display for SortError { 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 std::error::Error for SortError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::UnresolvedObject(err) => Some(err), _ => None, } } } #[cfg(test)] mod test { use std::cell::RefCell; use super::*; use crate::{ ir::{ asg::{ DataType, Dim, FragmentText, IdentObject, Source, TransitionResult, }, legacyir::SymDtype, }, sym::{GlobalSymbolIntern, SymbolId}, }; #[derive(Debug, Default, PartialEq)] struct StubIdentObject { given_declare: Option, given_extern: Option<(IdentKind, Source)>, given_resolve: Option<(IdentKind, Source)>, given_set_fragment: Option, fail_resolved: RefCell>, } impl<'i> IdentObjectData for StubIdentObject { fn name(&self) -> Option { self.given_declare } fn kind(&self) -> Option<&IdentKind> { self.given_resolve.as_ref().map(|args| &args.0) } fn src(&self) -> Option<&Source> { None } fn fragment(&self) -> Option<&FragmentText> { None } fn as_ident(&self) -> Option<&IdentObject> { None } } impl<'i> IdentObjectState for StubIdentObject { fn declare(ident: SymbolId) -> Self { Self { given_declare: Some(ident), ..Default::default() } } fn resolve( mut self, kind: IdentKind, src: Source, ) -> TransitionResult { self.given_resolve = Some((kind, src)); Ok(self) } fn resolved(&self) -> Result<&StubIdentObject, UnresolvedError> { if self.fail_resolved.borrow().is_some() { return Err(self.fail_resolved.replace(None).unwrap()); } Ok(self) } fn extern_( mut self, kind: IdentKind, src: Source, ) -> TransitionResult { self.given_extern = Some((kind, src)); Ok(self) } fn set_fragment( mut self, text: FragmentText, ) -> TransitionResult { self.given_set_fragment.replace(text); Ok(self) } } type Sut = BaseAsg; 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() -> SortResult<(), 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 = sort(&sut, &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() -> SortResult<(), 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 sort(&sut, &vec![sym_node]) { Ok(_) => panic!("Unexpected success - dependency is not in graph"), Err(SortError::MissingObjectKind(_)) => (), _ => { panic!("Incorrect error result when dependency is not in graph") } } Ok(()) } #[test] fn graph_sort_no_roots() -> SortResult<(), u16> { let mut sut = Sut::new(); let sym = "sym".intern(); let dep = "dep".intern(); let (_, _) = sut.add_dep_lookup(sym, dep); let sections = sort(&sut, &vec![])?; assert_eq!(Sections::new(), sections); Ok(()) } #[test] fn graph_sort_simple_cycle() -> SortResult<(), 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 = sort(&sut, &vec![sym_node]); let expected: Vec>> = vec![vec![dep_node.into(), sym_node.into()]]; match result { Ok(_) => panic!("sort did not detect cycle"), Err(SortError::Cycles(scc)) => assert_eq!(expected, scc), Err(e) => panic!("unexpected error: {}", e), } Ok(()) } #[test] fn graph_sort_two_simple_cycles() -> SortResult<(), 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 = sort(&sut, &vec![sym_node]); let expected: Vec>> = 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(SortError::Cycles(scc)) => assert_eq!(expected, scc), Err(e) => panic!("unexpected error: {}", e), } Ok(()) } #[test] fn graph_sort_no_cycle_with_edge_to_same_node() -> SortResult<(), 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 = sort(&sut, &vec![sym_node]); match result { Ok(_) => (), Err(e) => panic!("unexpected error: {}", e), } Ok(()) } #[test] fn graph_sort_cycle_with_a_few_steps() -> SortResult<(), 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 = sort(&sut, &vec![sym1_node]); let expected: Vec>> = vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]]; match result { Ok(_) => panic!("sort did not detect cycle"), Err(SortError::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( ) -> SortResult<(), 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 = sort(&sut, &vec![sym1_node]); let expected: Vec>> = vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]]; match result { Ok(_) => panic!("sort did not detect cycle"), Err(SortError::Cycles(scc)) => assert_eq!(expected, scc), Err(e) => panic!("unexpected error: {}", e), } Ok(()) } #[test] fn graph_sort_cyclic_bookended_by_functions() -> SortResult<(), 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 = sort(&sut, &vec![sym1_node]); let expected: Vec>> = vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]]; match result { Ok(_) => panic!("sort did not detect cycle"), Err(SortError::Cycles(scc)) => assert_eq!(expected, scc), Err(e) => panic!("unexpected error: {}", e), } Ok(()) } #[test] fn graph_sort_cyclic_function_ignored() -> SortResult<(), 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 = sort(&sut, &vec![sym_node]); match result { Ok(_) => (), Err(e) => panic!("unexpected error: {}", e), } Ok(()) } #[test] fn graph_sort_cyclic_function_is_bookended() -> SortResult<(), 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 = sort(&sut, &vec![sym1_node]); let expected: Vec>> = vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]]; match result { Ok(_) => panic!("sort did not detect cycle"), Err(SortError::Cycles(scc)) => assert_eq!(expected, scc), Err(e) => panic!("unexpected error: {}", e), } Ok(()) } #[test] fn graph_sort_ignore_non_linked() -> SortResult<(), 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 = sort(&sut, &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() -> SortResult<(), 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 = sort(&sut, &vec![node]) .expect_err("expected sort failure due to unresolved identifier"); match result { SortError::UnresolvedObject(err) => { assert_eq!(expected, err); } _ => panic!("expected SortError::Unresolved: {:?}", result), } Ok(()) } }