{- Tock: a compiler for parallel languages Copyright (C) 2007, 2008, 2009 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 . -} -- | 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.State import Control.Monad.Trans import Data.Generics (Data) import Data.Graph.Inductive hiding (mapSnd) 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 Data.Generics.Alloy.Route import Errors import ExSet import FlowAlgorithms import FlowGraph import FlowUtils 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 progress "- - Building flow graph" g' <- buildFlowGraph labelUsageFunctions t (g, roots) <- case g' of Left err -> dieP (findMeta t) err Right (g,rs,_) -> return (g,rs) progress "- - Analysing flow graph" 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 g' <- labelMapWithNodeIdM (addBK reach cons g) g progress "- - Checking flow graph for CREW" checkPar (nodeRep . snd) (joinCheckParFunctions (checkArrayUsage NameShared) (checkPlainVarUsage NameShared)) g' progress "- - Checking parallel assignments" checkParAssignUsage g' t progress "- - Checking PROC arguments" checkProcCallArgsUsage g' t -- mapM_ (checkInitVar (findMeta t) g) roots debug "Completed usage checking" 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 $ concatMap allVarsInBK bk allVarsInBK :: BackgroundKnowledge -> [A.Variable] allVarsInBK (Equal a b) = listifyDepth (const True) a ++ listifyDepth (const True) b allVarsInBK (LessThanOrEqual a b) = listifyDepth (const True) a ++ listifyDepth (const True) b allVarsInBK (RepBoundsIncl v a b) = v : (listifyDepth (const True) a ++ listifyDepth (const True) b) 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 -> PassM (FNode PassM (BK, UsageLabel)) addBK mp mp2 g nid n = do im <- conInterMed return $ fmap ((,) $ followBK (map keepDefined (joined' im))) 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 :: And A.Expression consInQuestion = And $ fromMaybe [] $ Map.lookup nid mp2 conInterMed :: PassM (And (Or BackgroundKnowledge)) conInterMed = liftM mconcat $ mapM g $ deAnd consInQuestion where g :: A.Expression -> PassM (And (Or BackgroundKnowledge)) g (A.FunctionCall m fn [lhs, rhs]) -- If one of the components of an AND is unrecognised, we still keep -- the other part: | fn == bop "AND" = liftM2 mappend (g lhs) (g rhs) -- (A and B) or (C and D) = ((A and B) or C) and ((A and B) or D) -- = (A or C) and (B or C) and (A or D) and (B or D) -- -- If one component of an OR is unrecognised, we end up dropping both -- halves because we can no longer count on that BK (given A OR B, where -- we recognise A, since we can't tell if B is true, we actually have -- no information about A even if the branch is taken). We do know that -- if the branch is not taken, A cannot be true, but that's dealt with -- because a negated OR ends up as an AND, see above. | fn == bop "OR" = do lhs' <- g lhs >>* deAnd rhs' <- g rhs >>* deAnd return $ And $ map (\(x,y) -> x `mappend` y) $ product2 (lhs', rhs') | otherwise = do mOp <- builtInOperator fn case mOp of Nothing -> return mempty Just op -> let noAndOr :: PassM a -> PassM (And (Or a)) noAndOr = liftM (noAnd . noOr) in case op of "=" -> noAndOr $ return $ Equal lhs rhs "<=" -> noAndOr $ return $ LessThanOrEqual lhs rhs ">=" -> noAndOr $ return $ LessThanOrEqual rhs lhs "<" -> noAndOr $ do lhs_p1 <- addOne lhs return $ LessThanOrEqual lhs_p1 rhs ">" -> noAndOr $ do rhs_p1 <- addOne rhs return $ LessThanOrEqual rhs_p1 lhs "<>" -> liftM noAnd $ do lhs_p1 <- addOne lhs rhs_p1 <- addOne rhs return $ Or [LessThanOrEqual lhs_p1 rhs ,LessThanOrEqual rhs_p1 lhs] _ -> return mempty where bop n = A.Name emptyMeta $ occamDefaultOperator n [A.Bool, A.Bool] g (A.FunctionCall _ fn [rhs]) | A.nameName fn == occamDefaultOperator "NOT" [A.Bool] = g $ negateExpr rhs where -- It is much easier (and clearer) to do the negation in the AST rather -- than play around with De Morgan's laws and so on to figure out how -- to invert the conjunction of disjunctions negateExpr (A.FunctionCall _ fn [rhs]) | A.nameName fn == occamDefaultOperator "NOT" [A.Bool] = rhs negateExpr e@(A.FunctionCall m fn [lhs, rhs]) | fn == bop "AND" = A.FunctionCall m (bop "OR") [negateExpr lhs, negateExpr rhs] | fn == bop "OR" = A.FunctionCall m (bop "AND") [negateExpr lhs, negateExpr rhs] | otherwise = let pairs = [("<>", "=") ,("<=", ">") ,(">=", "<")] rev (a, b) = [(bop a, bop b), (bop b, bop a)] revOp = concatMap rev pairs in case lookup fn revOp of Just op' -> A.FunctionCall m op' [lhs, rhs] Nothing -> e -- Leave as is, because it won't be used anyway where bop n = A.Name emptyMeta $ occamDefaultOperator n [A.Bool, A.Bool] negateExpr e = e -- As above, leave as is g _ = return mempty values :: And (Var, Or BackgroundKnowledge) values = And [ (Var v, Or $ catMaybes [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 :: And (Var, BackgroundKnowledge) repBK = And . 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 (subOneInt $ addExprsInt low count) -- How to join: -- repBK is just anded stuff, so we join that on to every conjunction in that -- variable. values and constraints are And/Or, so we need to do some work to turn it into -- Or/And, and combine the two in a cartesian-product-like operation. joined :: And (Or BackgroundKnowledge) -> Or (Map.Map Var (And BackgroundKnowledge)) joined interMed = Or [ possVal `union` makeMap possCon `union` (Map.map noAnd $ Map.fromList (deAnd repBK)) | possVal <- makeNonEmpty Map.empty $ deOr convValues , possCon <- makeNonEmpty (And []) $ deOr convCon ] where union :: Map.Map Var (And BackgroundKnowledge) -> Map.Map Var (And BackgroundKnowledge) -> Map.Map Var (And BackgroundKnowledge) union = Map.unionWith mappend makeNonEmpty :: a -> [a] -> [a] makeNonEmpty x [] = [x] makeNonEmpty _ xs = xs makeMap :: And BackgroundKnowledge -> Map.Map Var (And BackgroundKnowledge) makeMap (And bks) = Map.fromListWith mappend $ concat [zip (map Var $ allVarsInBK bk) (repeat $ noAnd bk) | bk <- bks] convValues :: Or (Map.Map Var (And BackgroundKnowledge)) convValues = Or $ map (Map.fromListWith mappend) $ map (mapSnd noAnd) $ productN $ map (\(x, y) -> zip (repeat x) (deOr y)) $ deAnd values convCon :: Or (And BackgroundKnowledge) convCon = Or $ map And $ productN $ map deOr $ deAnd interMed joined' :: And (Or BackgroundKnowledge) -> BK joined' interMed = map (Map.map deAnd) $ deOr (joined interMed) -- 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) => NameAttr -> (Meta, ParItems (BK, UsageLabel)) -> m () checkPlainVarUsage sharedAttr (m, p) = check p where addBK :: BK -> Vars -> m VarsBK addBK bk vs = do let read = setToMap (readVars vs) bk splitUsed <- splitEnds' $ Set.toList $ usedVars vs splitWritten <- concatMapM splitEnds (Map.toList $ writtenVars vs) >>* Map.fromList let used = Map.fromList (zip splitUsed (repeat ([], bk))) return $ VarsBK read ((Map.map (\me -> (maybeToList me, bk)) splitWritten) `Map.union` used) splitEnds' = liftM (map fst) . concatMapM splitEnds . flip zip (repeat ()) splitEnds :: (Var, a) -> m [(Var, a)] splitEnds (Var v, x) = do t <- astTypeOf v case (t, v) of -- Push the direction up to the array, not outside: (A.Chan {}, A.SubscriptedVariable m sub v') -> return [(Var $ A.SubscriptedVariable m sub $ A.DirectedVariable m dir v', x) | dir <- [A.DirInput, A.DirOutput]] (A.Chan {}, _) -> return [(Var $ A.DirectedVariable (findMeta v) dir v, x) | dir <- [A.DirInput, A.DirOutput]] _ -> return [(Var v, x)] reps (RepParItem r p) = r : reps p reps (SeqItems _) = [] reps (ParItems ps) = concatMap reps ps getVars :: ParItems (BK, UsageLabel) -> m (Map.Map Var (Maybe BK, Maybe BK)) getVars (SeqItems ss) = liftM foldUnionVarsBK $ sequence [addBK bk $ nodeVars u | (bk, u) <- ss] getVars (ParItems ps) = liftM (foldl (Map.unionWith join) Map.empty) (mapM 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 rawSharedNames <- getCompState >>* csNameAttr >>* Map.filter (Set.member sharedAttr) >>* Map.keysSet -- We add in the directed versions of each (channel or not) so that -- we make sure to ignore c? when c is shared: let allSharedNames = Set.fromList $ concatMap (map UsageCheckUtils.Var . flip applyAll [id, A.DirectedVariable emptyMeta A.DirInput , A.DirectedVariable emptyMeta A.DirOutput] . A.Variable emptyMeta . A.Name emptyMeta) $ Set.toList rawSharedNames let decl = concatMap getDecl ps filt <- filterPlain vars <- mapM getVars ps let examineVars = map (filterMapByKey filt . (`difference` (Set.fromList decl `Set.union` allSharedNames))) vars examineVars' <- mapM (filterMapByKeyM (liftM not . isSharedType)) examineVars checkCREW examineVars' where difference m s = m `Map.difference` setToMap s () isSharedType :: Var -> m Bool isSharedType v = do t <- astTypeOf v case t of A.ChanDataType _ sh _ -> return $ sh == A.Shared A.ChanEnd _ sh _ -> return $ sh == A.Shared _ -> return False -- 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 "" 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 "" showCodeExSet (NormalSet s) = do ss <- mapM showCode (Set.toList s) return $ "{" ++ joinWith ", " ss ++ "}" checkInitVarPass :: Pass A.AST 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, AlloyARoute (A.Structured t) (OneOpMRoute A.Process) BaseOpMRoute) => (A.Process -> Bool) -> FlowGraph' m a t -> A.Structured t -> [(A.Process, a)] findAllProcess f g t = Map.elems $ Map.intersectionWith (,) astMap nodeMap where nodeMap :: Map.Map [Int] a nodeMap = Map.fromList $ mapMaybe getProcess $ map snd $ labNodes g astMap :: Map.Map [Int] A.Process astMap = Map.fromList $ map (revPair . transformPair id routeId) $ listifyDepthRoute (f . fst) t getProcess :: FNode' t m a -> Maybe ([Int], a) getProcess n = case getNodeFunc n of AlterProcess f -> Just (routeId f, getNodeData n) _ -> Nothing checkParAssignUsage :: forall m t. (CSMR m, Die m, MonadIO m, Data t, AlloyARoute (A.Structured t) (OneOpMRoute A.Process) BaseOpMRoute ) => 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 debug $ "Checking assignment at: " ++ show m checkPlainVarUsage NameShared (m, mockedupParItems) checkArrayUsage NameShared (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, AlloyARoute (A.Structured t) (OneOpMRoute A.Process) BaseOpMRoute ) => 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 (A.ProcCall _ _ [_], _) = return () checkArgs (p@(A.ProcCall m _ _), (bk, _)) = do debug $ "Checking PROC call at " ++ show m vars <- getVarProcCall p let mockedupParItems = fmap ((,) bk) $ ParItems [SeqItems [Usage Nothing Nothing Nothing v] | v <- vars] checkPlainVarUsage NameAliasesPermitted (m, mockedupParItems) checkArrayUsage NameAliasesPermitted (m, mockedupParItems) debug $ "Done checking PROC call" -- 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 () -- Don't remove data types, in case the backend wants them: doSpec (A.Spec _ (A.Specification _ _ (A.DataType {})) _) = 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 ()