diff --git a/Makefile.am b/Makefile.am index ef9b62c..ce59134 100644 --- a/Makefile.am +++ b/Makefile.am @@ -85,6 +85,7 @@ tock_SOURCES_hs += backends/TLP.hs tock_SOURCES_hs += checks/ArrayUsageCheck.hs tock_SOURCES_hs += checks/Omega.hs tock_SOURCES_hs += checks/RainUsageCheck.hs +tock_SOURCES_hs += checks/UsageCheckAlgorithms.hs tock_SOURCES_hs += checks/UsageCheckUtils.hs tock_SOURCES_hs += common/AST.hs tock_SOURCES_hs += common/CompState.hs diff --git a/checks/ArrayUsageCheck.hs b/checks/ArrayUsageCheck.hs index e0283c1..e8a9528 100644 --- a/checks/ArrayUsageCheck.hs +++ b/checks/ArrayUsageCheck.hs @@ -35,6 +35,7 @@ import Omega import Pass import ShowCode import Types +import UsageCheckAlgorithms import UsageCheckUtils import Utils diff --git a/checks/RainUsageCheck.hs b/checks/RainUsageCheck.hs index fe8e47b..d906d07 100644 --- a/checks/RainUsageCheck.hs +++ b/checks/RainUsageCheck.hs @@ -20,7 +20,7 @@ with this program. If not, see . -- the control-flow graph stuff, hence the use of functions that match the dictionary -- of functions in FlowGraph. This is also why we don't drill down into processes; -- the control-flow graph means that we only need to concentrate on each node that isn't nested. -module RainUsageCheck (checkInitVar, findReachDef) where +module RainUsageCheck (checkInitVar) where import Control.Monad.Identity import Data.Graph.Inductive @@ -36,7 +36,6 @@ import FlowGraph import Metadata import ShowCode import UsageCheckUtils -import Utils {- Near the beginning, this piece of code was too clever for itself and applied processVarW using "everything". @@ -221,55 +220,3 @@ checkInitVar graph startNode do readVars <- showCodeExSet v writtenVars <- showCodeExSet vs dieP (getMeta n) $ "Variable read from is not written to before-hand, sets are read: " ++ show readVars ++ " and written: " ++ show writtenVars - --- | Returns either an error, or map *from* the node with a read, *to* the node whose definitions might be available at that point - --- I considered having the return type be Map Var (Map Node x)) rather than Map (Var,Node) x, but the time for lookup --- will be identical (log N + log V in the former case, log (V*N) in the latter), and having a pair seemed simpler. --- TODO correct that comment! -findReachDef :: forall m. Monad m => FlowGraph m (Maybe Decl, Vars) -> Node -> Either String (Map.Map Node (Map.Map Var (Set.Set Node))) -findReachDef graph startNode - = do r <- flowAlgorithm graphFuncs (nodes graph) startNode - -- These lines remove the maps where the variable is not read in that particular node: - let r' = Map.mapWithKey (\n -> Map.filterWithKey (readInNode' n)) r - return $ Map.filter (not . Map.null) r' - where - graphFuncs :: GraphFuncs Node EdgeLabel (Map.Map Var (Set.Set Node)) - graphFuncs = GF - { - nodeFunc = processNode - ,prevNodes = lpre graph - ,nextNodes = lsuc graph - ,initVal = Map.empty - ,defVal = Map.empty - } - - readInNode' :: Node -> Var -> a -> Bool - readInNode' n v _ = readInNode v (lab graph n) - - readInNode :: Var -> Maybe (FNode m (Maybe Decl, Vars)) -> Bool - readInNode v (Just (Node (_,(_,Vars read _ _),_))) = Set.member v read - - writeNode :: FNode m (Maybe Decl, Vars) -> Set.Set Var - writeNode (Node (_,(_,Vars _ written _),_)) = written - - -- | A confusiing function used by processNode. It takes a node and node label, and uses - -- these to form a multi-map modifier function that replaces all node-sources for variables - -- written to by the given with node with a singleton set containing the given node. - -- That is, nodeLabelToMapInsert N (Node (_,Vars _ written _ _)) is a function that replaces - -- the sets for each v (v in written) with a singleton set {N}. - nodeLabelToMapInsert :: Node -> FNode m (Maybe Decl, Vars) -> Map.Map Var (Set.Set Node) -> Map.Map Var (Set.Set Node) - nodeLabelToMapInsert n = foldFuncs . (map (\v -> Map.insert v (Set.singleton n) )) . Set.toList . writeNode - - processNode :: (Node, EdgeLabel) -> Map.Map Var (Set.Set Node) -> Maybe (Map.Map Var (Set.Set Node)) -> Map.Map Var (Set.Set Node) - processNode (n,_) inputVal mm = mergeMultiMaps modifiedInput prevAgg - where - prevAgg :: Map.Map Var (Set.Set Node) - prevAgg = fromMaybe Map.empty mm - - modifiedInput :: Map.Map Var (Set.Set Node) - modifiedInput = (maybe id (nodeLabelToMapInsert n) $ lab graph n) inputVal - - -- | Merges two "multi-maps" (maps to sets) using union - mergeMultiMaps :: (Ord k, Ord a) => Map.Map k (Set.Set a) -> Map.Map k (Set.Set a) -> Map.Map k (Set.Set a) - mergeMultiMaps = Map.unionWith (Set.union) diff --git a/checks/RainUsageCheckTest.hs b/checks/RainUsageCheckTest.hs index 065f118..d2bba03 100644 --- a/checks/RainUsageCheckTest.hs +++ b/checks/RainUsageCheckTest.hs @@ -31,6 +31,7 @@ import FlowGraph import Metadata import RainUsageCheck import TestUtils hiding (Var) +import UsageCheckAlgorithms import UsageCheckUtils import Utils diff --git a/checks/UsageCheckAlgorithms.hs b/checks/UsageCheckAlgorithms.hs new file mode 100644 index 0000000..c5f4498 --- /dev/null +++ b/checks/UsageCheckAlgorithms.hs @@ -0,0 +1,142 @@ +{- +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 . +-} + +module UsageCheckAlgorithms (checkPar, findReachDef, joinCheckParFunctions) where + +import Data.Graph.Inductive +import qualified Data.Map as Map +import Data.Maybe +import qualified Data.Set as Set + +import FlowAlgorithms +import FlowGraph +import Metadata +import UsageCheckUtils +import Utils + +joinCheckParFunctions :: Monad m => ((Meta, ParItems a) -> m b) -> ((Meta, ParItems a) -> m c) -> ((Meta, ParItems a) -> m (b,c)) +joinCheckParFunctions f g x = seqPair (f x, g x) + +-- | Given a function to check a list of graph labels and a flow graph, +-- returns a list of monadic actions (slightly +-- more flexible than a monadic action giving a list) that will check +-- all PAR items in the flow graph +checkPar :: forall m a b. Monad m => ((Meta, ParItems a) -> m b) -> FlowGraph m a -> [m b] +checkPar f g = map f allParItems + where + allStartParEdges :: Map.Map Int [(Node,Node)] + allStartParEdges = foldl (\mp (s,e,n) -> Map.insertWith (++) n [(s,e)] mp) Map.empty $ mapMaybe tagStartParEdge $ labEdges g + + tagStartParEdge :: (Node,Node,EdgeLabel) -> Maybe (Node,Node,Int) + tagStartParEdge (s,e,EStartPar n) = Just (s,e,n) + tagStartParEdge _ = Nothing + + allParItems :: [(Meta, ParItems a)] + allParItems = map makeEntry $ map findNodes $ Map.toList allStartParEdges + where + findNodes :: (Int,[(Node,Node)]) -> (Node,[ParItems a]) + findNodes (n,ses) = (undefined, [SeqItems (followUntilEdge e (EEndPar n)) | (_,e) <- ses]) + + makeEntry :: (Node,[ParItems a]) -> (Meta, ParItems a) + makeEntry (_,xs) = (emptyMeta {- TODO fix this again -} , ParItems xs) + + -- | We need to follow all edges out of a particular node until we reach + -- an edge that matches the given edge. So what we effectively need + -- is a depth-first or breadth-first search (DFS or BFS), that terminates + -- on a given edge, not on a given node. Therefore the DFS/BFS algorithms + -- that come with the inductive graph package are not very suitable as + -- they return node lists or edge lists, but we need a node list terminated + -- on a particular edge. + -- + -- So, we shall attempt our own algorithm! The algorithm for DFS given in + -- the library is effectively: + -- + -- dfs :: Graph gr => [Node] -> gr a b -> [Node] + -- dfs [] _ = [] + -- dfs _ g | isEmpty g = [] + -- dfs (v:vs) g = case match v g of + -- (Just c,g') -> node' c:dfs (suc' c++vs) g' + -- (Nothing,g') -> dfs vs g' + -- where node' :: Context a b -> Node and suc' :: Context a b -> [Node] + -- + -- We want to stop the DFS branch either when we find no nodes following the current + -- one (already effectively taken care of in the algorithm above; suc' will return + -- the empty list) or when the edge we are meant to take matches the given edge. + followUntilEdge :: Node -> EdgeLabel -> [a] + followUntilEdge startNode endEdge = customDFS [startNode] g + where + customDFS :: [Node] -> FlowGraph m a -> [a] + customDFS [] _ = [] + customDFS _ g | isEmpty g = [] + customDFS (v:vs) g = case match v g of + (Just c, g') -> labelItem c : customDFS (customSucc c ++ vs) g' + (Nothing, g') -> customDFS vs g' + + labelItem :: Context (FNode m a) EdgeLabel -> a + labelItem c = let (Node (_,x,_)) = lab' c in x + + customSucc :: Context (FNode m a) EdgeLabel -> [Node] + customSucc c = [n | (n,e) <- lsuc' c, e /= endEdge] + +-- | Returns either an error, or map *from* the node with a read, *to* the node whose definitions might be available at that point +findReachDef :: forall m. Monad m => FlowGraph m (Maybe Decl, Vars) -> Node -> Either String (Map.Map Node (Map.Map Var (Set.Set Node))) +findReachDef graph startNode + = do r <- flowAlgorithm graphFuncs (nodes graph) startNode + -- These lines remove the maps where the variable is not read in that particular node: + let r' = Map.mapWithKey (\n -> Map.filterWithKey (readInNode' n)) r + return $ Map.filter (not . Map.null) r' + where + graphFuncs :: GraphFuncs Node EdgeLabel (Map.Map Var (Set.Set Node)) + graphFuncs = GF + { + nodeFunc = processNode + ,prevNodes = lpre graph + ,nextNodes = lsuc graph + ,initVal = Map.empty + ,defVal = Map.empty + } + + readInNode' :: Node -> Var -> a -> Bool + readInNode' n v _ = readInNode v (lab graph n) + + readInNode :: Var -> Maybe (FNode m (Maybe Decl, Vars)) -> Bool + readInNode v (Just (Node (_,(_,Vars read _ _),_))) = Set.member v read + + writeNode :: FNode m (Maybe Decl, Vars) -> Set.Set Var + writeNode (Node (_,(_,Vars _ written _),_)) = written + + -- | A confusiing function used by processNode. It takes a node and node label, and uses + -- these to form a multi-map modifier function that replaces all node-sources for variables + -- written to by the given with node with a singleton set containing the given node. + -- That is, nodeLabelToMapInsert N (Node (_,Vars _ written _ _)) is a function that replaces + -- the sets for each v (v in written) with a singleton set {N}. + nodeLabelToMapInsert :: Node -> FNode m (Maybe Decl, Vars) -> Map.Map Var (Set.Set Node) -> Map.Map Var (Set.Set Node) + nodeLabelToMapInsert n = foldFuncs . (map (\v -> Map.insert v (Set.singleton n) )) . Set.toList . writeNode + + processNode :: (Node, EdgeLabel) -> Map.Map Var (Set.Set Node) -> Maybe (Map.Map Var (Set.Set Node)) -> Map.Map Var (Set.Set Node) + processNode (n,_) inputVal mm = mergeMultiMaps modifiedInput prevAgg + where + prevAgg :: Map.Map Var (Set.Set Node) + prevAgg = fromMaybe Map.empty mm + + modifiedInput :: Map.Map Var (Set.Set Node) + modifiedInput = (maybe id (nodeLabelToMapInsert n) $ lab graph n) inputVal + + -- | Merges two "multi-maps" (maps to sets) using union + mergeMultiMaps :: (Ord k, Ord a) => Map.Map k (Set.Set a) -> Map.Map k (Set.Set a) -> Map.Map k (Set.Set a) + mergeMultiMaps = Map.unionWith (Set.union) diff --git a/checks/UsageCheckUtils.hs b/checks/UsageCheckUtils.hs index 5792781..189d181 100644 --- a/checks/UsageCheckUtils.hs +++ b/checks/UsageCheckUtils.hs @@ -16,20 +16,16 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -} -module UsageCheckUtils (checkPar, customVarCompare, Decl(..), emptyVars, getVarProc, joinCheckParFunctions, labelFunctions, ParItems(..), transformParItems, Var(..), Vars(..), vars) where +module UsageCheckUtils (customVarCompare, Decl(..), emptyVars, getVarProc, labelFunctions, ParItems(..), transformParItems, Var(..), Vars(..), vars) where import Data.Generics hiding (GT) -import Data.Graph.Inductive -import qualified Data.Map as Map import Data.Maybe import qualified Data.Set as Set import qualified AST as A import Errors import FlowGraph -import Metadata import ShowCode -import Utils newtype Var = Var A.Variable deriving (Show) @@ -98,72 +94,6 @@ foldUnionVars = foldl unionVars emptyVars mapUnionVars :: (a -> Vars) -> [a] -> Vars mapUnionVars f = foldUnionVars . (map f) - -joinCheckParFunctions :: Monad m => ((Meta, ParItems a) -> m b) -> ((Meta, ParItems a) -> m c) -> ((Meta, ParItems a) -> m (b,c)) -joinCheckParFunctions f g x = seqPair (f x, g x) - --- | Given a function to check a list of graph labels and a flow graph, --- returns a list of monadic actions (slightly --- more flexible than a monadic action giving a list) that will check --- all PAR items in the flow graph -checkPar :: forall m a b. Monad m => ((Meta, ParItems a) -> m b) -> FlowGraph m a -> [m b] -checkPar f g = map f allParItems - where - allStartParEdges :: Map.Map Int [(Node,Node)] - allStartParEdges = foldl (\mp (s,e,n) -> Map.insertWith (++) n [(s,e)] mp) Map.empty $ mapMaybe tagStartParEdge $ labEdges g - - tagStartParEdge :: (Node,Node,EdgeLabel) -> Maybe (Node,Node,Int) - tagStartParEdge (s,e,EStartPar n) = Just (s,e,n) - tagStartParEdge _ = Nothing - - allParItems :: [(Meta, ParItems a)] - allParItems = map makeEntry $ map findNodes $ Map.toList allStartParEdges - where - findNodes :: (Int,[(Node,Node)]) -> (Node,[ParItems a]) - findNodes (n,ses) = (undefined, [SeqItems (followUntilEdge e (EEndPar n)) | (_,e) <- ses]) - - makeEntry :: (Node,[ParItems a]) -> (Meta, ParItems a) - makeEntry (_,xs) = (emptyMeta {- TODO fix this again -} , ParItems xs) - - -- | We need to follow all edges out of a particular node until we reach - -- an edge that matches the given edge. So what we effectively need - -- is a depth-first or breadth-first search (DFS or BFS), that terminates - -- on a given edge, not on a given node. Therefore the DFS/BFS algorithms - -- that come with the inductive graph package are not very suitable as - -- they return node lists or edge lists, but we need a node list terminated - -- on a particular edge. - -- - -- So, we shall attempt our own algorithm! The algorithm for DFS given in - -- the library is effectively: - -- - -- dfs :: Graph gr => [Node] -> gr a b -> [Node] - -- dfs [] _ = [] - -- dfs _ g | isEmpty g = [] - -- dfs (v:vs) g = case match v g of - -- (Just c,g') -> node' c:dfs (suc' c++vs) g' - -- (Nothing,g') -> dfs vs g' - -- where node' :: Context a b -> Node and suc' :: Context a b -> [Node] - -- - -- We want to stop the DFS branch either when we find no nodes following the current - -- one (already effectively taken care of in the algorithm above; suc' will return - -- the empty list) or when the edge we are meant to take matches the given edge. - followUntilEdge :: Node -> EdgeLabel -> [a] - followUntilEdge startNode endEdge = customDFS [startNode] g - where - customDFS :: [Node] -> FlowGraph m a -> [a] - customDFS [] _ = [] - customDFS _ g | isEmpty g = [] - customDFS (v:vs) g = case match v g of - (Just c, g') -> labelItem c : customDFS (customSucc c ++ vs) g' - (Nothing, g') -> customDFS vs g' - - labelItem :: Context (FNode m a) EdgeLabel -> a - labelItem c = let (Node (_,x,_)) = lab' c in x - - customSucc :: Context (FNode m a) EdgeLabel -> [Node] - customSucc c = [n | (n,e) <- lsuc' c, e /= endEdge] - - --Gets the (written,read) variables of a piece of an occam program: --For subscripted variables used as Lvalues , e.g. a[b] it should return a[b] as written-to and b as read