language-boogie-0.1: Language/Boogie/DataFlow.hs
-- | Data-flow analysis on Boogie code
module Language.Boogie.DataFlow (liveVariables, liveInputVariables) where
import Language.Boogie.AST
import Language.Boogie.Util
import Language.Boogie.Position hiding (gen)
import Language.Boogie.BasicBlocks
import Data.List
import Data.Map (Map, (!))
import qualified Data.Map as M
import Data.Set (Set)
import qualified Data.Set as S
{- Interface -}
-- | 'liveInputVariables' @sig def@ :
-- Input parameters (in the order they appear in @sig@) and global names,
-- whose initial value might be read by the procedure implementation @def@
liveInputVariables :: PSig -> PDef -> ([Id], [Id])
liveInputVariables sig def = let
body = pdefBody def
liveVars = liveVariables (attachContractChecks sig def)
liveLocals = filter (`elem` liveVars) (map itwId (fst body))
liveIns = filter (`elem` liveVars) (pdefIns def)
liveOuts = filter (`elem` liveVars) (pdefOuts def)
liveGlobals = liveVars \\ (liveLocals ++ liveIns ++ liveOuts)
in (liveIns, liveGlobals)
-- | Identifiers whose initial value might be read in body
liveVariables :: Map Id [Statement] -> [Id]
liveVariables body = let
empty = M.map (const S.empty) body
insertExitBlock i = M.insert i (transition (body ! i) S.empty)
entry0 = S.foldr insertExitBlock empty (exitBlocks body)
changed0 = M.keysSet body <-> exitBlocks body
oldVariables = S.unions (map (\block -> S.unions (map genOld block)) (M.elems body))
in S.toList (oldVariables `S.union` (analyse body entry0 empty changed0 ! startLabel))
{- Implementation -}
-- | Analyse live variable in body,
-- starting from live variables at the entry to each block entry,
-- live variables at the exit of each block exit,
-- and the set of blocks whose exit set might have changed changed.
analyse :: Map Id [Statement] -> Map Id (Set Id) -> Map Id (Set Id) -> Set Id -> Map Id (Set Id)
analyse body entry exit changed = if S.null changed
then entry
else let
(i, changed') = S.deleteFindMax changed
newExit = setUnions $ S.map (entry !) (successors body i)
newEntry = transition (body ! i) newExit
exit' = M.insert i newExit exit
entry' = M.insert i newEntry entry
changed'' = if entry ! i == newEntry then changed' else changed' <+> predecessors body i
in analyse body entry' exit' changed''
(<+>) = (S.union)
(<->) = (S.\\)
-- | Union of a set of sets
setUnions sets = S.foldl S.union S.empty sets
-- | Variables that are live before a sequence of statements sts,
-- if the final live variables are exit
transition :: [Statement] -> Set Id -> Set Id
transition sts exit = foldr transition1 exit sts
where
transition1 st exit = exit <-> kill st <+> gen st
-- | Variables that are not live anymore as a result of st
kill :: Statement -> Set Id
kill st = case node st of
Havoc ids -> S.fromList ids
Assign lhss _ -> S.fromList (map fst lhss)
Call lhss _ _ -> S.fromList lhss
otherwise -> S.empty
-- | Variables that become live as a result of st
gen :: Statement -> Set Id
gen st = genTwoState fst st
-- | Variables whose pre-state is mentioned in st
genOld :: Statement -> Set Id
genOld st = genTwoState snd st
-- | Variables mentioned in st in either current state or old state
genTwoState :: (([Id], [Id]) -> [Id]) -> Statement -> Set Id
genTwoState select st = case node st of
Predicate (SpecClause _ _ e) -> (S.fromList . select . freeVarsTwoState) e
Assign lhss rhss -> let
allSubscipts = concat $ concatMap snd lhss
subsciptedLhss = [fst lhs | lhs <- lhss, not (null (snd lhs))] -- Left-hand sides with a subscript are also read (consider desugaring)
in S.unions (map (S.fromList . select . freeVarsTwoState) (rhss ++ allSubscipts)) <+> S.fromList subsciptedLhss
Call _ _ args -> S.unions (map (S.fromList . select . freeVarsTwoState) args)
CallForall _ args -> S.unions (map (S.fromList . select . freeVarsTwoState') args)
otherwise -> S.empty
where
freeVarsTwoState' Wildcard = ([], [])
freeVarsTwoState' (Expr e) = freeVarsTwoState e
-- | Blocks in body that end with a return statement
exitBlocks :: Map Id [Statement] -> Set Id
exitBlocks body = M.keysSet $ M.filter isExit body
where
isExit block = case node (last block) of
Return -> True
_ -> False
-- | Blocks in body that have an outgoing edge to label
predecessors :: Map Id [Statement] -> Id -> Set Id
predecessors body label = M.keysSet $ M.filter (goesTo label) body
where
goesTo label block = case node (last block) of
Goto lbs -> label `elem` lbs
_ -> False
-- | Blocks in body that have an incoming edge from label
successors :: Map Id [Statement] -> Id -> Set Id
successors body label = case node (last (body ! label)) of
Goto lbs -> S.fromList lbs
_ -> S.empty
-- | Body of the implementation def of procedure sig with pre- and postcondition checks embedded;
-- (used to extract live variables from contracts)
attachContractChecks :: PSig -> PDef -> Map Id [Statement]
attachContractChecks sig def = let
preChecks = map (attachPos (pdefPos def) . Predicate . subst sig) (psigRequires sig)
postChecks = map (attachPos (pdefPos def) . Predicate . subst sig) (psigEnsures sig)
subst sig (SpecClause t f e) = SpecClause t f (paramSubst sig def e)
attachPreChecks = M.adjust (preChecks ++) startLabel (snd (pdefBody def))
attachPostChecks block = let jump = last block
in case node jump of
Return -> init block ++ postChecks ++ [jump]
_ -> block
in M.map attachPostChecks attachPreChecks