{- 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 UsageCheck (checkPar, customVarCompare, Decl, labelFunctions, ParItems(..), Var(..), Vars(..)) where import Data.Generics 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 newtype Var = Var A.Variable customVarCompare :: A.Variable -> A.Variable -> Ordering customVarCompare (A.Variable _ (A.Name _ _ lname)) (A.Variable _ (A.Name _ _ rname)) = compare lname rname -- TODO the rest instance Eq Var where a == b = EQ == compare a b instance Ord Var where compare (Var a) (Var b) = customVarCompare a b data Vars = Vars { readVars :: Set.Set Var ,writtenVars :: Set.Set Var ,usedVars :: Set.Set Var -- for channels, barriers, etc } data Decl = ScopeIn String | ScopeOut String deriving (Show, Eq) data ParItems a = ParItem a | ParItems [ParItems a] | RepParItem A.Replicator (ParItems a) emptyVars :: Vars emptyVars = Vars Set.empty Set.empty Set.empty mkReadVars :: [Var] -> Vars mkReadVars ss = Vars (Set.fromList ss) Set.empty Set.empty mkWrittenVars :: [Var] -> Vars mkWrittenVars ss = Vars Set.empty (Set.fromList ss) Set.empty mkUsedVars :: [Var] -> Vars mkUsedVars vs = Vars Set.empty Set.empty (Set.fromList vs) vars :: [Var] -> [Var] -> [Var] -> Vars vars mr mw u = Vars (Set.fromList mr) (Set.fromList mw) (Set.fromList u) unionVars :: Vars -> Vars -> Vars unionVars (Vars mr mw u) (Vars mr' mw' u') = Vars (mr `Set.union` mr') (mw `Set.union` mw') (u `Set.union` u') foldUnionVars :: [Vars] -> Vars foldUnionVars = foldl unionVars emptyVars mapUnionVars :: (a -> Vars) -> [a] -> Vars mapUnionVars f = foldUnionVars . (map f) -- | Given a function to check a list of graph labels, a flow graph -- and a starting node, 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,[a]) findNodes (n,ses) = (undefined, concat [followUntilEdge e (EEndPar n) | (_,e) <- ses]) makeEntry :: (Node,[a]) -> (Meta, ParItems a) makeEntry (_,x) = (emptyMeta {- TODO fix this again -} , ParItems $ map ParItem x) -- | 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 --For subscripted variables used as expressions, e.g. a[b] it should return a[b],b as read (with no written-to) getVarProc :: A.Process -> Vars getVarProc (A.Assign _ vars expList) --Join together: = unionVars --The written-to variables on the LHS: (foldUnionVars (map processVarW vars)) --All variables read on the RHS: (getVarExpList expList) getVarProc (A.GetTime _ v) = processVarW v getVarProc (A.Wait _ _ e) = getVarExp e getVarProc (A.Output _ chanVar outItems) = (processVarUsed chanVar) `unionVars` (mapUnionVars getVarOutputItem outItems) where getVarOutputItem :: A.OutputItem -> Vars getVarOutputItem (A.OutExpression _ e) = getVarExp e getVarOutputItem (A.OutCounted _ ce ae) = (getVarExp ce) `unionVars` (getVarExp ae) getVarProc (A.Input _ chanVar (A.InputSimple _ iis)) = (processVarUsed chanVar) `unionVars` (mapUnionVars getVarInputItem iis) where getVarInputItem :: A.InputItem -> Vars getVarInputItem (A.InCounted _ cv av) = mkWrittenVars [variableToVar cv,variableToVar av] getVarInputItem (A.InVariable _ v) = mkWrittenVars [variableToVar v] --TODO process calls getVarProc _ = emptyVars {- Near the beginning, this piece of code was too clever for itself and applied processVarW using "everything". The problem with this is that given var@(A.SubscriptedVariable _ sub arrVar), the functions would be recursively applied to sub and arrVar. processVarW should return var as written to, but never the subscripts in sub; those subscripts are not written to! Therefore processVarW must *not* be applied using the generics library, and instead should always be applied directly to an A.Variable. Internally it uses the generics library to process the subscripts (using getVarExp) -} --Pull out all the subscripts into the read category, but leave the given var in the written category: processVarW :: A.Variable -> Vars processVarW v = mkWrittenVars [variableToVar v] processVarR :: A.Variable -> Vars processVarR v = mkReadVars [variableToVar v] processVarUsed :: A.Variable -> Vars processVarUsed v = mkUsedVars [variableToVar v] variableToVar :: A.Variable -> Var variableToVar = Var getVarExpList :: A.ExpressionList -> Vars getVarExpList (A.ExpressionList _ es) = foldUnionVars $ map getVarExp es getVarExpList (A.FunctionCallList _ _ es) = foldUnionVars $ map getVarExp es --TODO record stuff in passed as well? getVarExp :: A.Expression -> Vars getVarExp = everything unionVars (emptyVars `mkQ` getVarExp') where --Only need to deal with the two cases where we can see an A.Variable directly; --the generic recursion will take care of nested expressions, and even the expressions used as subscripts getVarExp' :: A.Expression -> Vars getVarExp' (A.SizeVariable _ v) = processVarR v getVarExp' (A.ExprVariable _ v) = processVarR v getVarExp' _ = emptyVars getVarSpec :: A.Specification -> Vars getVarSpec = const emptyVars -- TODO getDecl :: (String -> Decl) -> A.Specification -> Maybe Decl getDecl _ _ = Nothing -- TODO labelFunctions :: forall m. Die m => GraphLabelFuncs m (Maybe Decl, Vars) labelFunctions = GLF { labelExpression = pair (const Nothing) getVarExp ,labelExpressionList = pair (const Nothing) getVarExpList ,labelDummy = const (return (Nothing, emptyVars)) ,labelProcess = pair (const Nothing) getVarProc --don't forget about the variables used as initialisers in declarations (hence getVarSpec) ,labelScopeIn = pair (getDecl ScopeIn) getVarSpec ,labelScopeOut = pair (getDecl ScopeOut) (const emptyVars) } where pair :: (a -> b) -> (a -> c) -> (a -> m (b,c)) pair f0 f1 x = return (f0 x, f1 x)