clash-lib-1.8.0: src/Clash/Util/Graph.hs
{-|
Copyright : (C) 2018, QBayLogic
License : BSD2 (see the file LICENSE)
Maintainer : Christiaan Baaij <christiaan.baaij@gmail.com>
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 qualified Clash.Data.UniqMap as UniqMap
import Clash.Driver.Types (BindingMap, Binding (bindingTerm))
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 . (`UniqMap.find` bindingsMap)) (UniqMap.keys cg)
where
cg = callGraph bindingsMap tm