{-| Copyright : (C) 2018, QBayLogic License : BSD2 (see the file LICENSE) Maintainer : Christiaan Baaij Collection of utilities -} module Clash.Util.Graph ( topSort , reverseTopSort , callGraphBindings ) where import Data.Tuple (swap) import Data.Foldable (foldlM) import qualified Data.IntMap.Strict as IntMap import qualified Data.IntSet as IntSet import Clash.Core.Var (Id) import Clash.Core.Term (Term) import Clash.Driver.Types (BindingMap, Binding (bindingTerm)) import Clash.Unique (lookupUniqMap', keysUniqMap) import Clash.Normalize.Util (callGraph) data Marker = Temporary | Permanent headSafe :: [a] -> Maybe a headSafe [] = Nothing headSafe (a:_) = Just a topSortVisit' :: IntMap.IntMap [Int] -- ^ Edges -> IntSet.IntSet -- ^ Unmarked nodes -> IntMap.IntMap Marker -- ^ Marked nodes -> [Int] -- ^ Sorted so far -> Int -- ^ Node to visit -> Either String (IntSet.IntSet, IntMap.IntMap Marker, [Int]) topSortVisit' edges unmarked marked sorted node = case IntMap.lookup node marked of Just Permanent -> Right (unmarked, marked, sorted) Just Temporary -> Left "cycle detected: cannot topsort cyclic graph" Nothing -> do let marked' = IntMap.insert node Temporary marked let unmarked' = IntSet.delete node unmarked let nodeToM = IntMap.findWithDefault [] node edges (unmarked'', marked'', sorted'') <- foldlM visit (unmarked', marked', sorted) nodeToM let marked''' = IntMap.insert node Permanent marked'' return (unmarked'', marked''', node : sorted'') where visit (unmarked', marked', sorted') node' = topSortVisit' edges unmarked' marked' sorted' node' topSortVisit :: IntMap.IntMap [Int] -- ^ Edges -> IntSet.IntSet -- ^ Unmarked nodes -> IntMap.IntMap Marker -- ^ Marked nodes -> [Int] -- ^ Sorted so far -> Int -- ^ Node to visit -> Either String (IntSet.IntSet, IntMap.IntMap Marker, [Int]) topSortVisit edges unmarked marked sorted node = do (unmarked', marked', sorted') <- topSortVisit' edges unmarked marked sorted node case headSafe (IntSet.toList unmarked') of Nothing -> return (unmarked', marked', sorted') Just node' -> topSortVisit edges unmarked' marked' sorted' node' -- | See: https://en.wikipedia.org/wiki/Topological_sorting. This function -- errors if edges mention nodes not mentioned in the node list or if the -- given graph contains cycles. topSort :: [(Int, a)] -- ^ Nodes -> [(Int, Int)] -- ^ Edges -> Either String [a] -- ^ Error message or topologically sorted nodes topSort [] [] = Right [] topSort [] _edges = Left "Node list was empty, but edges non-empty" topSort nodes@(node:_) edges = do _ <- mapM (\(n, m) -> checkNode n >> checkNode m) edges (_, _, sorted) <- topSortVisit edges' (IntMap.keysSet nodes') IntMap.empty [] (fst node) mapM lookup' sorted where nodes' = IntMap.fromList nodes edges' = foldl insert IntMap.empty edges -- Construction functions for quick lookup of edges from n to m, given n insert im (n, m) = IntMap.alter (insert' m) n im insert' m Nothing = Just [m] insert' m (Just ms) = Just (m:ms) -- Lookup node in nodes map. If not present, yield error lookup' n = case IntMap.lookup n nodes' of Nothing -> Left ("Node " ++ show n ++ " in edge list, but not in node list.") Just n' -> Right n' -- Check if edge is valid (i.e., mentioned nodes are in node list) checkNode n | IntMap.notMember n nodes' = Left ("Node " ++ show n ++ " in edge list, but not in node list.") | otherwise = Right n -- | Same as `reverse (topSort nodes edges)` if alternative representations are -- considered the same. That is, topSort might produce multiple answers and -- still deliver on its promise of yielding a topologically sorted node list. -- Likewise, this function promises __one__ of those lists in reverse, but not -- necessarily the reverse of topSort itself. reverseTopSort :: [(Int, a)] -- ^ Nodes -> [(Int, Int)] -- ^ Edges -> Either String [a] -- ^ Reversely, topologically sorted nodes reverseTopSort nodes edges = topSort nodes (map swap edges) -- | Get all the terms corresponding to a call graph callGraphBindings :: BindingMap -- ^ All bindings -> Id -- ^ Root of the call graph -> [Term] callGraphBindings bindingsMap tm = map (bindingTerm . (bindingsMap `lookupUniqMap'`)) (keysUniqMap cg) where cg = callGraph bindingsMap tm