hic-0.0.0.1: src/Language/Cimple/Hic/Inference/Iteration.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Language.Cimple.Hic.Inference.Iteration
( feature
) where
import Control.Monad.State.Strict (State, modify)
import qualified Control.Monad.State.Strict as State
import Data.Fix (Fix (..), foldFix)
import Data.Foldable (foldMap)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (listToMaybe)
import Data.Text (Text)
import qualified Language.Cimple as C
import Language.Cimple.Hic.Ast (HicNode (..), Node,
NodeF (..))
import Language.Cimple.Hic.Context (Context (..))
import Language.Cimple.Hic.Feature (Feature (..))
import Language.Cimple.Hic.Inference.Utils (dummyLexeme, getTypeName)
import Language.Cimple.Hic.Program.Types (Program (..))
feature :: Feature
feature = Feature
{ featureName = "Iteration"
, featureGather = \_ ctx -> ctx
, featureInfer = infer
, featureValidate = validate
, featureLower = lower
}
-- | Phase 2: Infer Iteration constructs.
infer :: Context -> FilePath -> Node (C.Lexeme Text) -> State Bool (Node (C.Lexeme Text))
infer ctx _file node = snd (foldFix alg node) Map.empty
where
alg f =
let original = Fix (fmap fst f)
in (original, \env -> do
let env' = updateEnv env (fmap fst f)
f' <- traverse (\(_, transform) -> transform env') f
let n' = Fix f'
case attemptTransform ctx env' n' of
Just newNode -> modify (const True) >> return newNode
Nothing -> return n')
attemptTransform :: Context -> Map Text Text -> Node (C.Lexeme Text) -> Maybe (Node (C.Lexeme Text))
attemptTransform _ctx _env node =
case node of
Fix (CimpleNode (C.ForStmt lInit lCond lStep lBody)) ->
inferFor lInit lCond lStep lBody
Fix (CimpleNode (C.CompoundStmt stmts)) ->
Fix . CimpleNode . C.CompoundStmt <$> inferFind stmts
_ -> Nothing
inferFor :: Node (C.Lexeme Text) -> Node (C.Lexeme Text) -> Node (C.Lexeme Text) -> Node (C.Lexeme Text) -> Maybe (Node (C.Lexeme Text))
inferFor lInit lCond lStep lBody = do
(itL, _) <- matchInit lInit
(itL2, _, _) <- matchCond lCond
let it = C.lexemeText itL
if it /= C.lexemeText itL2 then Nothing else do
_ <- matchStep it lStep
containers <- identifyContainers it lBody
if isAssigned it lBody then Nothing else do
let feBody' = substitute it containers lBody
return $ Fix $ HicNode $ ForEach
{ feIterators = replicate (length containers) itL
, feInit = lInit
, feCond = lCond
, feStep = lStep
, feContainers = containers
, feBody = feBody'
, feHasIndex = hasIndex feBody'
}
matchInit :: Node (C.Lexeme Text) -> Maybe (C.Lexeme Text, Node (C.Lexeme Text))
matchInit (Fix (CimpleNode (C.VarDeclStmt (Fix (CimpleNode (C.VarDecl _ name []))) (Just val)))) =
Just (name, val)
matchInit (Fix (CimpleNode (C.AssignExpr (Fix (CimpleNode (C.VarExpr name))) C.AopEq val))) =
Just (name, val)
matchInit _ = Nothing
matchCond :: Node (C.Lexeme Text) -> Maybe (C.Lexeme Text, C.BinaryOp, Node (C.Lexeme Text))
matchCond (Fix (CimpleNode (C.BinaryExpr (Fix (CimpleNode (C.VarExpr name))) op bound))) =
Just (name, op, bound)
matchCond _ = Nothing
matchStep :: Text -> Node (C.Lexeme Text) -> Maybe ()
matchStep it (Fix (CimpleNode (C.UnaryExpr op (Fix (CimpleNode (C.VarExpr name))))))
| it == C.lexemeText name && (op == C.UopIncr) = Just ()
matchStep it (Fix (CimpleNode (C.AssignExpr (Fix (CimpleNode (C.VarExpr name))) C.AopPlus val)))
| it == C.lexemeText name && isOne val = Just ()
matchStep _ _ = Nothing
isOne :: Node (C.Lexeme Text) -> Bool
isOne = foldFix $ \case
CimpleNode (C.LiteralExpr C.Int l) -> C.lexemeText l == "1"
_ -> False
identifyContainers :: Text -> Node (C.Lexeme Text) -> Maybe [Node (C.Lexeme Text)]
identifyContainers it bodyNode =
let usages = findUsages it bodyNode
indexings = [ c | Indexing c <- usages ]
in if null indexings
then Nothing
else do
let containerMap = Map.fromList [ (C.removeSloc (stripHic c), c) | c <- indexings ]
let uniqueContainers = Map.elems containerMap
if all isStable uniqueContainers then Just uniqueContainers else Nothing
isStable :: Node (C.Lexeme Text) -> Bool
isStable node = fst $ foldFix alg node
where
alg f = (stable, constant)
where
constant = case f of
CimpleNode (C.LiteralExpr _ _) -> True
_ -> False
stable = case f of
CimpleNode (C.VarExpr _) -> True
CimpleNode (C.MemberAccess (s, _) _) -> s
CimpleNode (C.PointerAccess (s, _) _) -> s
CimpleNode (C.ArrayAccess (s, _) (_, c)) -> s && c
CimpleNode (C.ParenExpr (s, _)) -> s
_ -> False
isAssigned :: Text -> Node (C.Lexeme Text) -> Bool
isAssigned it node = fst $ foldFix alg node
where
alg :: NodeF (C.Lexeme Text) (Bool, Bool) -> (Bool, Bool)
alg f = (assigned, isIter)
where
isIter = case f of
CimpleNode (C.VarExpr i) -> C.lexemeText i == it
_ -> False
assigned = (case f of
CimpleNode (C.AssignExpr (_, lhsIsIter) _ (rhsAssigned, _)) -> lhsIsIter || rhsAssigned
CimpleNode (C.UnaryExpr op (eAssigned, eIsIter)) ->
(op `elem` [C.UopIncr, C.UopDecr] && eIsIter) || eAssigned
_ -> any fst f)
stripHic :: Node (C.Lexeme Text) -> C.Node (C.Lexeme Text)
stripHic = foldFix $ \case
CimpleNode f -> Fix f
HicNode h ->
case h of
IterationElement _ c -> Fix (C.ArrayAccess c (Fix (C.VarExpr (dummyLexeme "dummy"))))
IterationIndex _ -> Fix (C.VarExpr (dummyLexeme "dummy"))
_ -> error "Unexpected HicNode in identifyContainers"
data Usage = Indexing (Node (C.Lexeme Text))
data UsageInfo = UsageInfo
{ uiNode :: Node (C.Lexeme Text)
, uiUsages :: [Usage]
, uiIsIter :: Bool
}
findUsages :: Text -> Node (C.Lexeme Text) -> [Usage]
findUsages it = uiUsages . foldFix alg
where
alg :: NodeF (C.Lexeme Text) UsageInfo -> UsageInfo
alg f = UsageInfo
{ uiNode = Fix (fmap uiNode f)
, uiUsages = usages
, uiIsIter = isIter
}
where
isIter = case f of
CimpleNode (C.VarExpr i) -> C.lexemeText i == it
_ -> False
usages = (case f of
CimpleNode (C.ArrayAccess container idx) ->
if uiIsIter idx then [Indexing (uiNode container)] else []
HicNode (IterationElement _ container) -> [Indexing (uiNode container)]
_ -> []) ++ foldMap uiUsages f
isVar :: Text -> Node (C.Lexeme Text) -> Bool
isVar it = foldFix $ \case
CimpleNode (C.VarExpr i) -> C.lexemeText i == it
HicNode (IterationIndex i) -> C.lexemeText i == it
_ -> False
substitute :: Text -> [Node (C.Lexeme Text)] -> Node (C.Lexeme Text) -> Node (C.Lexeme Text)
substitute it containers = foldFix $ \f ->
case f of
CimpleNode (C.ArrayAccess c idx)
| isVar it idx ->
case listToMaybe [ con | con <- containers, C.removeSloc (stripHic c) == C.removeSloc (stripHic con) ] of
Just con | length containers == 1 ->
case extractLexeme idx of
Just l -> Fix (HicNode (IterationElement l con))
Nothing -> error "substitute: expected VarExpr"
_ -> Fix (CimpleNode (C.ArrayAccess c (Fix (HicNode (IterationIndex (dummyLexeme it))))))
CimpleNode (C.VarExpr i)
| C.lexemeText i == it ->
Fix (HicNode (IterationIndex i))
_ -> Fix f
where
extractLexeme :: Node (C.Lexeme Text) -> Maybe (C.Lexeme Text)
extractLexeme = foldFix $ \case
CimpleNode (C.VarExpr l) -> Just l
CimpleNode (C.ParenExpr e) -> e
HicNode (IterationIndex l) -> Just l
_ -> Nothing
hasIndex :: Node (C.Lexeme Text) -> Bool
hasIndex = foldFix $ \case
HicNode (IterationIndex _) -> True
f -> any id f
inferFind :: [Node (C.Lexeme Text)] -> Maybe [Node (C.Lexeme Text)]
inferFind stmts = do
(prefix, loop, suffix) <- findLoop stmts
case loop of
Fix (CimpleNode (C.ForStmt lInit lCond lStep lBody)) -> do
(itL, _) <- matchInit lInit
(itL2, _, _) <- matchCond lCond
let it = C.lexemeText itL
if it /= C.lexemeText itL2 then Nothing else do
_ <- matchStep it lStep
(lPred, foundAction) <- matchFindBody it lBody
containers <- identifyContainers it lPred
container <- listToMaybe containers
let newStmt = Fix $ HicNode $ Find
{ fIterator = itL
, fInit = lInit
, fCond = lCond
, fStep = lStep
, fContainer = container
, fPredicate = substitute it [container] lPred
, fOnFound = substitute it [container] foundAction
, fOnMissing = listToMaybe suffix
}
return $ prefix ++ [newStmt] ++ drop 1 suffix
Fix (HicNode (ForEach (itL:_) lInit lCond lStep _ lBody _)) -> do
let it = C.lexemeText itL
(lPred, foundAction) <- matchFindBody it lBody
-- ForEach already has containers, but we want the one used in lPred
containers' <- identifyContainers it lPred
container <- listToMaybe containers'
let newStmt = Fix $ HicNode $ Find
{ fIterator = itL
, fInit = lInit
, fCond = lCond
, fStep = lStep
, fContainer = container
, fPredicate = substitute it [container] lPred
, fOnFound = substitute it [container] foundAction
, fOnMissing = listToMaybe suffix
}
return $ prefix ++ [newStmt] ++ drop 1 suffix
_ -> Nothing
findLoop :: [Node (C.Lexeme Text)] -> Maybe ([Node (C.Lexeme Text)], Node (C.Lexeme Text), [Node (C.Lexeme Text)])
findLoop [] = Nothing
findLoop (s@(Fix (CimpleNode C.ForStmt{})) : ss) = Just ([], s, ss)
findLoop (s@(Fix (HicNode ForEach{})) : ss) = Just ([], s, ss)
findLoop (s : ss) = do
(p, l, su) <- findLoop ss
return (s:p, l, su)
matchFindBody :: Text -> Node (C.Lexeme Text) -> Maybe (Node (C.Lexeme Text), Node (C.Lexeme Text))
matchFindBody it (Fix (CimpleNode (C.CompoundStmt [Fix (CimpleNode (C.IfStmt cond then_ Nothing)) ]))) =
if usesIterator it cond then Just (cond, then_) else Nothing
matchFindBody it (Fix (CimpleNode (C.IfStmt cond (Fix (CimpleNode (C.CompoundStmt [then_]))) Nothing))) =
if usesIterator it cond then Just (cond, then_) else Nothing
matchFindBody it (Fix (CimpleNode (C.IfStmt cond then_ Nothing))) =
if usesIterator it cond then Just (cond, then_) else Nothing
matchFindBody _ _ = Nothing
usesIterator :: Text -> Node (C.Lexeme Text) -> Bool
usesIterator it = foldFix $ \case
CimpleNode (C.VarExpr i) | C.lexemeText i == it -> True
HicNode (IterationIndex i) | C.lexemeText i == it -> True
HicNode (IterationElement i _) | C.lexemeText i == it -> True
f -> any id f
updateEnv :: Map Text Text -> NodeF (C.Lexeme Text) (Node (C.Lexeme Text)) -> Map Text Text
updateEnv env (CimpleNode (C.VarDecl ty name _)) =
case getTypeName ty of
Just tyName -> Map.insert (C.lexemeText name) tyName env
Nothing -> env
updateEnv env (CimpleNode (C.VarDeclStmt (Fix (CimpleNode (C.VarDecl ty name _))) _)) =
case getTypeName ty of
Just tyName -> Map.insert (C.lexemeText name) tyName env
Nothing -> env
updateEnv env (CimpleNode (C.FunctionDefn _ (Fix (CimpleNode (C.FunctionPrototype _ _ params))) _)) =
foldl updateFromParam env params
where
updateFromParam e (Fix (CimpleNode (C.VarDecl ty name _))) =
case getTypeName ty of
Just tyName -> Map.insert (C.lexemeText name) tyName e
Nothing -> e
updateFromParam e _ = e
updateEnv env (CimpleNode (C.FunctionPrototype _ _ params)) =
foldl updateFromParam env params
where
updateFromParam e (Fix (CimpleNode (C.VarDecl ty name _))) =
case getTypeName ty of
Just tyName -> Map.insert (C.lexemeText name) tyName e
Nothing -> e
updateFromParam e _ = e
updateEnv env _ = env
paraFix :: Functor f => (f (Fix f, a) -> a) -> Fix f -> a
paraFix f = snd . foldFix (\x -> (Fix (fmap fst x), f x))
validate :: Context -> Program (C.Lexeme Text) -> [Text]
validate _ ctx = concatMap validateFile (Map.toList (progAsts ctx))
where
validateFile (file, nodes) = concatMap (checkIteration file) nodes
checkIteration file = paraFix $ \f ->
checkNode file (Fix (fmap fst f)) ++ foldMap snd f
checkNode file (Fix (CimpleNode (C.ForStmt lInit lCond lStep lBody))) =
case matchInit lInit of
Just (itL, _) ->
let it = C.lexemeText itL in
case matchCond lCond of
Just (itL2, _, _) | it == C.lexemeText itL2 ->
case matchStep it lStep of
Just () -> checkIterationCandidate file itL lBody
Nothing -> []
_ -> []
_ -> []
checkNode _ _ = []
checkIterationCandidate file itL lBody =
let it = C.lexemeText itL
usages = findUsages it lBody
indexings = [ c | Indexing c <- usages ]
in if null indexings then []
else if isAssigned it lBody then [C.sloc file itL <> ": Induction variable '" <> it <> "' is modified within the loop body. Refactor to enable for_each lifting."]
else case Map.elems $ Map.fromList [ (stripHic c, c) | c <- indexings ] of
cs | any (not . isStable) cs -> [C.sloc file itL <> ": Container expression is not stable. Refactor to enable for_each lifting."]
_ -> []
lower :: forall l. HicNode l (C.Node l) -> Maybe (C.Node l)
lower (ForEach _is lInit lCond lStep _cons lBody _hi) =
Just $ Fix $ C.ForStmt lInit lCond lStep lBody
lower (Find _i lInit lCond lStep _con lPred foundAction m) =
let body = Fix $ C.CompoundStmt [ Fix $ C.IfStmt lPred foundAction Nothing ]
in Just $ Fix $ C.Group $
[ Fix $ C.ForStmt lInit lCond lStep body ]
++ maybe [] (:[]) m
lower (IterationElement i c) =
Just $ Fix $ C.ArrayAccess c (Fix (C.VarExpr i))
lower (IterationIndex i) =
Just $ Fix $ C.VarExpr i
lower _ = Nothing