tock-mirror/flow/FlowAlgorithms.hs

166 lines
7.7 KiB
Haskell

{-
Tock: a compiler for parallel languages
Copyright (C) 2007 University of Kent
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 2 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 <http://www.gnu.org/licenses/>.
-}
module FlowAlgorithms where
import Control.Monad.Error
import qualified Data.Map as Map
import Data.Maybe
import qualified Data.Set as Set
import Utils
data GraphFuncs n e result = GF {
-- (Node, Edge) -> current value for said node -> current aggregate effect for
-- the current node -> new aggregate effect
-- If it is the first node to be processed for this iteration, it will be
-- given Nothing, otherwise the result is fed back when processing the next
-- node. The second parameter is from the last iteration.
nodeFunc :: (n,e) -> result -> Maybe result -> result
-- For forward data-flow, this should be the predecessor nodes. For backward
-- data-flow, this should be the successor nodes
,nodesToProcess :: n -> [(n,e)]
-- For forward data-flow, this should be the successor nodes. For backward
-- data-flow, this should be the predecessor nodes
,nodesToReAdd :: n -> [(n,e)]
-- defVal is the default starting value for all the nodes (except the first node)
,defVal :: result
-- Used to give a helpful error message about a node
,userErrLabel :: n -> String
}
-- | Given the graph functions, a list of nodes and an entry node, performs
-- an iterative data-flow analysis. All the nodes in the list should be connected to
-- the starting node, and there should be no nodes without nodes to process
-- (i.e. where nodesToProcess returns the empty list) in the list except the
-- starting node.
--
-- The implication of the above is that you should /not/ pass as the second
-- parameter all the nodes in the graph (unless you /know/ that it is fully
-- connected). Instead you should pass the connected nodes. If you are doing
-- forward data flow (using @nodesToProcess = lpre graph@), you can find the connected
-- nodes using @(dfs [initNode] graph)@. If you are doing backward data flow
-- (using @nodesToProcess = lsuc graph@), you can find the connected nodes using
-- @(rdfs [initNode] graph)@.
--
-- The general idea of iterative data-flow is that all nodes start out with
-- a default "guessed" value. Then each node is processed in turn by using
-- an accumulating value (to start with Nothing), and the values associated with
-- each of the nodesToProcess in the graph. This algorithm is performed repeatedly,
-- processing all nodes, and if a node changes its value, re-adding all its
-- nodes in the other direction (nodesToReAdd) to the worklist to be processed again.
--
-- The function is agnostic as to the representation of the graph, provided
-- it supports the two required operations (nodesToProcess and nodesToReAdd).
-- It can also do forward or backward data flow by just swapping those two
-- functions over.
flowAlgorithm :: forall n e result. (Ord n, Show n, Eq result) =>
GraphFuncs n e result -- ^ The set of functions to handle the graph.
-> [n] -- ^ The list of all nodes to process
-> (n, result) -- ^ The starting node (can also be in the list) and its
-- starting guess
-> Either String (Map.Map n result) -- ^ Either an error or the map from
-- nodes to results
flowAlgorithm funcs nodes (startNode, startVal)
= iterate
nonStartNodesSet
(Map.fromList $ (startNode, startVal):(zip nonStartNodes (repeat (defVal funcs))))
where
-- The nodes list, with the start node removed:
nonStartNodes :: [n]
nonStartNodes = (filter ((/=) startNode) nodes)
nonStartNodesSet :: Set.Set n
nonStartNodesSet = Set.fromList nonStartNodes
allNodesSet :: Set.Set n
allNodesSet = Set.singleton startNode `Set.union` nonStartNodesSet
filtNodes :: [(n,e)] -> [(n,e)]
filtNodes = filter ((`Set.member` allNodesSet) . fst)
-- | Folds left, but with Either types involved. Gives an error if there
-- are no nodes in the given list at the start (i.e. when its second parameter
-- is Left). Otherwise feeds the aggregate result back round on each
-- iteration of the list, but stops at the first error while folding (so
-- a bit like foldM)
foldWithEither ::
(b -- ^ The list value being folded
-> Maybe result
-- ^ The current accumulated result. Nothing if it is the first item
-- in the list
-> Either String result
-- ^ Either give back an error or a new accumulated result
)
-> Either String result
-- ^ The starting value should be (Left errorMessageForEmptyList)
-- and thereafter it will be Right result.
-> [b] -- ^ The list to fold over
-> Either String result
-- ^ Either an error or the result. Errors can
-- be caused by a starting empty list, or an error in processing
-- an individual item
foldWithEither _ (Left err) [] = throwError $ "empty list for previous nodes in flowAlgorithm:"
++ err
foldWithEither _ (Right a) [] = return a
foldWithEither f ea (b:bs)
= do b' <- f b $ eitherToMaybe ea
foldWithEither f (Right b') bs
-- | Given a map from node to current results, a node and edge to process
-- (the node is from nodesToProcess, and the edge connects it to the current
-- node), an aggregate result (Nothing if nothing processed yet), returns
-- an error or a new result by processing the node
iterateNode :: Map.Map n result -> (n,e) -> Maybe result -> Either String result
iterateNode vals ne ma
= case Map.lookup (fst ne) vals of
Nothing -> throwError $ "Value not found for node edge: " ++ show (fst ne)
Just v -> return $ nodeFunc funcs ne v ma
-- | Iterates the dataflow analysis. It is given a set of nodes to process,
-- a map from nodes to current results, and iterates until it gives back
-- an error or the list of final results
iterate :: Set.Set n -> Map.Map n result -> Either String (Map.Map n result)
iterate workList vals
-- No nodes left to process, finished:
| Set.null workList = Right vals
| otherwise
-- Pick the next node from the list and remove it:
= do let (node, workList') = Set.deleteFindMin workList
-- Process that node:
total <- foldWithEither (iterateNode vals) (Left $
"Nodes still to process: " ++ show workList
++ " " ++ userErrLabel funcs node)
(filtNodes $ nodesToProcess funcs node)
nodeVal <- case Map.lookup node vals of
Nothing -> throwError "Could not find node during flowAlgorithm"
Just x -> return x
if total /= nodeVal
-- If the value has changed, that will cascade to affect all
-- its dependents, so add all
-- of them back to the work list:
then iterate (workList' `Set.union` (Set.fromList $
map fst $ filtNodes $ nodesToReAdd funcs node)) (Map.insert node total vals)
-- If the value hasn't changed, forget it and go on to the
-- next one:
else iterate workList' vals