tock-mirror/checks/Check.hs

490 lines
22 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/>.
-}
-- | This code implements various usage checking. It is designed to work with
-- 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 Check (checkInitVarPass, usageCheckPass, checkUnusedVar) where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Trans
import Data.Generics
import Data.Graph.Inductive
import Data.List hiding (union)
import qualified Data.Map as Map
import Data.Maybe
import Data.Monoid
import qualified Data.Set as Set
import ArrayUsageCheck
import qualified AST as A
import CheckFramework
import CompState
import Errors
import ExSet
import FlowAlgorithms
import FlowGraph
import FlowUtils
import GenericUtils
import Metadata
import Pass
import ShowCode
import Traversal
import Types
import UsageCheckAlgorithms
import UsageCheckUtils
import Utils
usageCheckPass :: A.AST -> PassM A.AST
usageCheckPass t = do g' <- buildFlowGraph labelUsageFunctions t
(g, roots) <- case g' of
Left err -> dieP (findMeta t) err
Right (g,rs,_) -> return (g,rs)
reach <- case mapM (findReachDef g) roots >>* foldl Map.union
Map.empty of
Left err -> dieP emptyMeta $ "findReachDef: " ++
err
Right r -> return r
cons <- case mapM (findConstraints g) roots
>>* foldl Map.union Map.empty of
Left err -> dieP emptyMeta $ "findConstraints:"
++ err
Right c -> return c
let g' = labelMapWithNodeId (addBK reach cons g) g
checkPar (nodeRep . snd)
(joinCheckParFunctions checkArrayUsage checkPlainVarUsage)
g'
checkParAssignUsage g' t
checkProcCallArgsUsage g' t
-- mapM_ (checkInitVar (findMeta t) g) roots
return t
-- | For each entry in the BK, finds all the variables involved in a given piece
-- of BK and adds the BK from all other variables. For example, let's say that
-- the BK for variable x is "x = y". This function will add, under the key of
-- x, the BK from variable y, which might be say, "y <= z + 1", at which point
-- it will add the BK for z to the entry for x and so on.
followBK :: BK -> BK
followBK = map followBK'
where
followBK' :: Map.Map Var [BackgroundKnowledge] -> Map.Map Var [BackgroundKnowledge]
followBK' m = Map.mapWithKey addAll m
where
addAll :: Var -> [BackgroundKnowledge] -> [BackgroundKnowledge]
addAll v = addAll' (Set.singleton v)
addAll' :: Set.Set Var -> [BackgroundKnowledge] -> [BackgroundKnowledge]
addAll' prev bk
| Set.null (next `Set.difference` prev) = bk
| otherwise = bk ++ addAll' (next `Set.union` prev)
(concat $ mapMaybe (flip Map.lookup m) (Set.toList $
next `Set.difference` prev))
where
next = Set.fromList $ map Var $ listify (const True :: A.Variable -> Bool) bk
data And a = And [a]
data Or a = Or [a]
instance Monoid (And a) where
mempty = And []
mappend (And a) (And b) = And (a ++ b)
instance Monoid (Or a) where
mempty = Or []
mappend (Or a) (Or b) = Or (a ++ b)
instance Functor And where
fmap f (And xs) = And $ map f xs
instance Functor Or where
fmap f (Or xs) = Or $ map f xs
deAnd :: And a -> [a]
deAnd (And xs) = xs
deOr :: Or a -> [a]
deOr (Or xs) = xs
noOr :: a -> Or a
noOr = Or . singleton
noAnd :: a -> And a
noAnd = And . singleton
addBK :: Map.Map Node (Map.Map Var (Set.Set (Maybe A.Expression))) ->
Map.Map Node [A.Expression] -> FlowGraph PassM UsageLabel ->
Node -> FNode PassM UsageLabel -> FNode PassM (BK, UsageLabel)
addBK mp mp2 g nid n = fmap ((,) $ followBK (map (keepDefined . Map.fromListWith (++)) $
productN $ conBK ++ repBK ++ values)) n
where
keepDefined :: Map.Map Var a -> Map.Map Var a
keepDefined m = Map.intersection m $ Map.fromList
[(Var (A.Variable emptyMeta (A.Name emptyMeta n)), ()) | n <- getNodeNames n]
nodeInQuestion :: Map.Map Var (Set.Set (Maybe A.Expression))
nodeInQuestion = fromMaybe Map.empty $ Map.lookup nid mp
consInQuestion :: [A.Expression]
consInQuestion = fromMaybe [] $ Map.lookup nid mp2
conInterMed :: ([Var], [[BackgroundKnowledge]])
conInterMed = f $ foldl (A.Dyadic emptyMeta (A.And)) (A.True emptyMeta) consInQuestion
where
f :: A.Expression -> ([Var], [[BackgroundKnowledge]])
f e = (map Var $ listify (const True) $ g e, g e)
g :: A.Expression -> [[BackgroundKnowledge]]
g (A.Dyadic _ op lhs rhs)
-- (A or B) and (C or D) = ((A or B) and C) or ((A or B) and D)
-- = (A and C) or (B and C) or (A and D) or (B and D)
| op == A.And = let l = g lhs
r = g rhs
in if null l || null r then l ++ r
else [a ++ b | a <- l, b <- r]
| op == A.Or = g lhs ++ g rhs
| op == A.Eq = [[Equal lhs rhs]]
| op == A.LessEq = [[LessThanOrEqual lhs rhs]]
| op == A.MoreEq = [[LessThanOrEqual rhs lhs]]
| op == A.Less = [[LessThanOrEqual (addOne lhs) rhs]]
| op == A.More = [[LessThanOrEqual (addOne rhs) lhs]]
| op == A.NotEq = [[LessThanOrEqual (addOne lhs) rhs]
,[LessThanOrEqual (addOne rhs) lhs]]
g (A.Monadic _ A.MonadicNot rhs)
= negate $ g rhs
where
-- We have something with a disjunctive normal form, that we need to
-- negate:
-- not ((A and B) or (C and D))
-- De Morgan:
-- (not (A and B)) and (not (C and D))
-- De Morgan again:
-- (not A or not B) and (not C or not D)
-- Distribution:
-- (not A and not C) or (not A and not D) or (not B and not C) or (not
-- B and not D)
--
-- So what we must do is do a cross-product across all the lists,
-- and negate each element. But this is futher complicated by the
-- way that Equals expands out to an ORed item.
negate orig = productN $ negateORs orig
-- Takes a list of things ORed, gives back a list of things ANDed
negateORs = map negateANDs
-- Takes a list of things ANDed, gives back a list of things ORed
negateANDs = concatMap negateItem
negateItem (Equal lhs rhs) = [LessThanOrEqual (addOne lhs) rhs
,LessThanOrEqual (addOne rhs) lhs]
negateItem (LessThanOrEqual lhs rhs) = [LessThanOrEqual (addOne rhs) lhs]
g _ = []
conBK :: [[(Var, [BackgroundKnowledge])]]
conBK = [zip (repeat v) (snd conInterMed) | v <- fst conInterMed]
-- Each list (xs) in the whole thing (xss) relates to a different variable
-- Each item in a list xs is a different possible constraint on that variable
-- (effectively joined together by OR)
-- The items in the list of BackgroundKnowledge are joined together with
-- AND
values :: [[(Var, [BackgroundKnowledge])]]
values = [ [(Var v, maybeToList $ fmap (Equal $ A.ExprVariable (findMeta v)
v) val) | val <- Set.toList vals]
| (Var v, vals) <- Map.toList nodeInQuestion]
-- Add bk based on replicator bounds
-- Search for the node containing the replicator definition,
-- TODO Then use background knowledge related to any variables mentioned in
-- the bounds *at that node* not at the current node-in-question
repBK :: [[(Var, [BackgroundKnowledge])]]
repBK = mapMaybe (fmap mkBK . nodeRep . getNodeData . snd) $ labNodes g
where
--TODO only really need consider the connected nodes...
mkBK :: (A.Name, A.Replicator) -> [(Var, [BackgroundKnowledge])]
mkBK (n, A.For _ low count _) = [(Var v, bk)]
where
m = A.nameMeta n
v = A.Variable m n
bk = [ RepBoundsIncl v low (subOne $ A.Dyadic m A.Add low count)]
-- filter out array accesses, leave everything else in:
filterPlain :: CSMR m => m (Var -> Bool)
filterPlain = return isPlain
where
isPlain (Var (A.SubscriptedVariable _ (A.SubscriptField {}) v)) = isPlain (Var v)
isPlain (Var (A.SubscriptedVariable {})) = False
isPlain _ = True
filterPlain' :: CSMR m => ExSet Var -> m (ExSet Var)
filterPlain' Everything = return Everything
filterPlain' (NormalSet s) = filterPlain >>* flip Set.filter s >>* NormalSet
data VarsBK = VarsBK {
readVarsBK :: Map.Map Var BK
,writtenVarsBK :: Map.Map Var ([A.Expression], BK)
}
-- | Unions all the maps into one, with possible BK for read, and possible BK for written.
-- Since BK is a list which is a disjunction of things, we concatenate the BK for
-- different things, because a variable access under conditions A, as well as a
-- variable access under conditions B, is equivalent to a variable access in which
-- condition A or condition B holds.
foldUnionVarsBK :: [VarsBK] -> Map.Map Var (Maybe BK, Maybe BK)
foldUnionVarsBK
-- unionWith, unlike intersectionWith, requires the same value type
-- for both maps in the union, so we must convert then join:
= foldl (Map.unionWith concatPair') Map.empty . map convert
where
joinPair (Just a, Nothing) (Nothing, Just b) = (Just a, Just b)
concatPair' (a,b) (c,d) = (join a c, join b d)
where
join Nothing Nothing = Nothing
join (Just a) Nothing = Just a
join Nothing (Just a) = Just a
join (Just a) (Just b) = Just (a++b)
convert :: VarsBK -> Map.Map Var (Maybe BK, Maybe BK)
convert (VarsBK r w) = Map.unionWith joinPair (Map.map mkR r) (Map.map mkW w)
mkR bk = (Just bk, Nothing)
mkW (_, bk) = (Nothing, Just bk)
checkPlainVarUsage :: forall m. (MonadIO m, Die m, CSMR m) => (Meta, ParItems (BK, UsageLabel)) -> m ()
checkPlainVarUsage (m, p) = check p
where
addBK :: BK -> Vars -> VarsBK
addBK bk vs = VarsBK (Map.fromAscList $ zip (Set.toAscList $ readVars vs) (repeat bk))
((Map.map (\me -> (maybeToList me, bk)) $ writtenVars vs)
`Map.union` Map.fromAscList (zip (Set.toAscList $ usedVars
vs) (repeat ([], bk))))
reps (RepParItem r p) = r : reps p
reps (SeqItems _) = []
reps (ParItems ps) = concatMap reps ps
getVars :: ParItems (BK, UsageLabel) -> Map.Map Var (Maybe BK, Maybe BK)
getVars (SeqItems ss) = foldUnionVarsBK $ [addBK bk $ nodeVars u | (bk, u) <- ss]
getVars (ParItems ps) = foldl (Map.unionWith join) Map.empty (map getVars ps)
where
join a b = (f (fst a) (fst b), f (snd a) (snd b))
f Nothing x = x
f x Nothing = x
f (Just x) (Just y) = Just (x ++ y)
getVars (RepParItem _ p) = getVars p
getDecl :: ParItems (BK, UsageLabel) -> [Var]
getDecl (ParItems ps) = concatMap getDecl ps
getDecl (RepParItem _ p) = getDecl p
getDecl (SeqItems ss) = mapMaybe
(fmap (Var . A.Variable emptyMeta . A.Name emptyMeta) . join . fmap getScopeIn . nodeDecl
. snd) ss
where
getScopeIn (ScopeIn _ n) = Just n
getScopeIn _ = Nothing
-- Check does not have to descend, because the overall checkPlainVarUsage function
-- will be called on every single PAR in the whole tree
check :: ParItems (BK, UsageLabel) -> m ()
check (SeqItems {}) = return ()
check (RepParItem _ p) = check (ParItems [p,p]) -- Easy way to check two replicated branches
-- We first need to ignore all names that are shared, or declared inside the
-- PAR that we are currently checking. After that, we need to find all the
-- variables written to in two of the maps in a list, and all the variables
-- written to in one of the maps and read in another. So we can find, for
-- all the variables written in a map, whether it is read in any other.
--
-- A quick way to do this is to do a fold-union across all the maps, turning
-- the values into lists that can then be scanned for any problems.
check (ParItems ps)
= do sharedNames <- getCompState >>* csNameAttr >>* Map.filter (== NameShared)
>>* Map.keysSet >>* (Set.map $ UsageCheckUtils.Var . A.Variable emptyMeta . A.Name emptyMeta)
let decl = concatMap getDecl ps
filt <- filterPlain
let examineVars =
map (filterMapByKey filt . (`difference` (Set.fromList decl `Set.union` sharedNames)))
(map getVars ps)
checkCREW examineVars
where
difference m s = m `Map.difference` (Map.fromAscList $ zip (Set.toAscList
s) (repeat ()))
-- We must compare each read against all writes after us in the list,
-- and each write against all reads and writes after us in the list:
checkCREW :: [Map.Map Var (Maybe BK, Maybe BK)] -> m ()
checkCREW [] = return ()
checkCREW (m:ms) = do let folded = foldl (Map.unionWith concatPair) Map.empty $
map (Map.map (transformPair maybeToList maybeToList)) ms
reads = Map.map (fromJust . fst) $ Map.filter (isJust . fst) m
writes = Map.map (fromJust . snd) $ Map.filter (isJust . snd) m
sequence_ [checkBKReps msg v bk bks
| (v, (bk, bks)) <- Map.toList $ Map.intersectionWith (,)
reads (Map.map snd folded)]
sequence_ [checkBKReps msg v bk (bks++bks')
| (v, (bk, (bks, bks'))) <- Map.toList $ Map.intersectionWith (,)
writes folded]
checkCREW ms
where
msg = "The following variables are written and (written-to/read-from) inside a PAR: % when: "
checkBKReps :: String -> Var -> BK -> [BK] -> m ()
checkBKReps _ _ _ [] = return ()
checkBKReps msg v bk bks
= do sol <- if null (reps p)
-- If there are no replicators, it's definitely dangerous:
then return $ Just "<always>"
else findRepSolutions (reps p) (bk:bks)
case sol of
Nothing -> return ()
Just sol' -> diePC m $ formatCode (msg ++ sol') v
showCodeExSet :: (CSMR m, Ord a, ShowOccam a, ShowRain a) => ExSet a -> m String
showCodeExSet Everything = return "<all-vars>"
showCodeExSet (NormalSet s)
= do ss <- mapM showCode (Set.toList s)
return $ "{" ++ concat (intersperse ", " ss) ++ "}"
checkInitVarPass :: Pass
checkInitVarPass = pass "checkInitVar" [] []
(passOnlyOnAST "checkInitVar" $ runChecks checkInitVar)
-- | Checks that no variable is used uninitialised. That is, it checks that every variable is written to before it is read.
checkInitVar :: CheckOptM ()
checkInitVar = forAnyFlowNode
(\(g, roots, _) -> sequence
[case flowAlgorithm (graphFuncs g) (dfs [r] g) (r, writeNode (fromJust $ lab g r)) of
Left err -> dieP emptyMeta err
Right x -> return x
| r <- roots] >>* foldl Map.union Map.empty)
checkInitVar'
-- We check that for every variable read in each node, it has already been written to by then
where
-- Gets all variables read-from in a particular node, and the node identifier
readNode :: UsageLabel -> ExSet Var
readNode u = NormalSet $ readVars $ nodeVars u
-- Gets all variables written-to in a particular node
writeNode :: Monad m => FNode m UsageLabel -> ExSet Var
writeNode nd = NormalSet $ Map.keysSet $ writtenVars $ nodeVars $ getNodeData nd
-- Nothing is treated as if were the set of all possible variables:
nodeFunction :: Monad m => FlowGraph m UsageLabel -> (Node, EdgeLabel) -> ExSet Var -> Maybe (ExSet Var) -> ExSet Var
nodeFunction graph (n,_) inputVal Nothing = union inputVal (maybe emptySet writeNode (lab graph n))
nodeFunction graph (n, EEndPar _) inputVal (Just prevAgg) = unions [inputVal,prevAgg,maybe emptySet writeNode (lab graph n)]
nodeFunction graph (n, _) inputVal (Just prevAgg) = intersection prevAgg $ union inputVal (maybe emptySet writeNode (lab graph n))
graphFuncs :: Monad m => FlowGraph m UsageLabel -> GraphFuncs Node EdgeLabel (ExSet Var)
graphFuncs graph = GF
{
nodeFunc = nodeFunction graph
,nodesToProcess = lpre graph
,nodesToReAdd = lsuc graph
,defVal = Everything
,userErrLabel = ("for node at: " ++) . show . fmap getNodeMeta . lab graph
}
checkInitVar' :: CheckOptFlowM (ExSet Var) ()
checkInitVar'
= do (v, vs) <- getFlowLabel >>* transformPair readNode (fromMaybe emptySet)
filtv <- filterPlain' v
filtvs <- filterPlain' vs
-- The read-from set should be a subset of the written-to set:
if filtv `isSubsetOf` filtvs then return () else
do vars <- showCodeExSet $ filtv `difference` filtvs
m <- getFlowMeta
warnP m WarnUninitialisedVariable $ "Variable(s) read from are not written to before-hand: " ++ vars
findAllProcess :: forall t m a. (Data t, Monad m) =>
(A.Process -> Bool) -> FlowGraph' m a t -> A.Structured t -> [(A.Process, a)]
findAllProcess f g t = filter (f . fst) $ mapMaybe getProcess $ map snd $ labNodes g
where
getProcess :: FNode' t m a -> Maybe (A.Process, a)
getProcess n = case getNodeFunc n of
AlterProcess f -> Just (routeGet f t, getNodeData n)
_ -> Nothing
checkParAssignUsage :: forall m t. (CSMR m, Die m, MonadIO m, Data t) =>
FlowGraph' m (BK, UsageLabel) t -> A.Structured t -> m ()
checkParAssignUsage g = mapM_ checkParAssign . findAllProcess isParAssign g
where
isParAssign :: A.Process -> Bool
isParAssign (A.Assign _ vs _) = length vs >= 2
isParAssign _ = False
-- | Need to check that all the destinations in a parallel assignment
-- are distinct. So we check plain variables, and array variables
checkParAssign :: (A.Process, (BK, UsageLabel)) -> m ()
checkParAssign (A.Assign m vs _, (bk, _))
= do checkPlainVarUsage (m, mockedupParItems)
checkArrayUsage (m, mockedupParItems)
where
mockedupParItems :: ParItems (BK, UsageLabel)
mockedupParItems = fmap ((,) bk) $ ParItems [SeqItems [Usage Nothing Nothing Nothing
$ processVarW v Nothing] | v <- vs]
checkProcCallArgsUsage :: forall m t. (CSMR m, Die m, MonadIO m, Data t) =>
FlowGraph' m (BK, UsageLabel) t -> A.Structured t -> m ()
checkProcCallArgsUsage g = mapM_ checkArgs . findAllProcess isProcCall g
where
isProcCall :: A.Process -> Bool
isProcCall (A.ProcCall {}) = True
isProcCall _ = False
-- | Need to check that all the destinations in a parallel assignment
-- are distinct. So we check plain variables, and array variables
checkArgs :: (A.Process, (BK, UsageLabel)) -> m ()
checkArgs (p@(A.ProcCall m _ _), (bk, _))
= do vars <- getVarProcCall p
let mockedupParItems = fmap ((,) bk) $
ParItems [SeqItems [Usage Nothing Nothing Nothing v]
| v <- vars]
checkPlainVarUsage (m, mockedupParItems)
checkArrayUsage (m, mockedupParItems)
-- This isn't actually just unused variables, it's all unused names (except PROCs)
checkUnusedVar :: CheckOptM ()
checkUnusedVar = forAnyASTStructBottomUpAccum doSpec
where
doSpec :: Data a => A.Structured a -> CheckOptASTM' [A.Name] (A.Structured a) ()
-- Don't touch PROCs, for now:
doSpec (A.Spec _ (A.Specification mspec name (A.Proc {})) scope) = return ()
-- DO NOT remove unused replicators!
doSpec (A.Spec _ (A.Specification mspec name (A.Rep {})) scope) = return ()
doSpec (A.Spec _ (A.Specification mspec name _) scope)
= do -- We can't remove _sizes arrays because the backend uses them for bounds
-- checks that are not explicit in the AST. We'll have to move the
-- bounds checking forward into the AST before we can remove them.
-- Making this more general, we don't actually remove any unused nonces.
nd <- lookupName name
when (A.ndNameSource nd == A.NameUser) $
do usedNames <- askAccum >>* delete name
-- ^ strip off one use of each name, since it's used in the spec
when (not $ A.nameName name `elem` map A.nameName usedNames) $
do warnPC mspec WarnUnusedVariable $ formatCode "Unused variable: %" name
modify (\st -> st { csNames = Map.delete (A.nameName name) (csNames st) })
substitute scope
doSpec _ = return ()