g2-0.2.0.0: src/G2/Language/PathConds.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module G2.Language.PathConds ( PathConds
, PCGroup (..)
, PathCond (..)
, HashedPathCond
, Constraint
, Assertion
, mkAssumePC
, mkSingletonAssumePC
, toUFMap
, toUFList
, empty
, fromList
, fromHashedList
, map
, mapHashedPCs
, map'
, filter
, alter
, alterHashed
, unionAlterHashed
, insert
, null
, number
, relatedSets
, scc
, varIdsInPC
, varNamesInPC
, allIds
, toList
, toHashedList
, toHashSet
, union
-- , intersection
-- , difference
, mergeWithAssumePCs
, hashedPC
, unhashedPC
, mapHashedPC) where
import qualified G2.Data.UFMap as UF
import G2.Language.AST
import G2.Language.Ids
import G2.Language.Naming
import G2.Language.Syntax
import Data.Coerce
import Data.Data (Data, Typeable)
import qualified Data.Foldable as F
import GHC.Generics (Generic)
import Data.Hashable
import qualified Data.HashSet as HS
import qualified Data.HashMap.Lazy as HM
import qualified Data.List as L
import Data.Maybe
import Data.Monoid (Monoid (..))
import Prelude hiding (map, filter, null)
import qualified Prelude as P (map)
import Data.Semigroup (Semigroup (..))
-- Conceptually, the path constraints are a graph, with (Maybe Name)'s Nodes.
-- Edges exist between any names that are in the same path constraint.
-- Strongly connected components in the graph must be checked and solved together.
-- A collection of path constraints- requirements on symbolic variables.
newtype PathConds = PathConds (UF.UFMap (Maybe Name) PCGroup)
deriving (Show, Eq, Read, Generic, Typeable, Data)
instance Hashable PathConds
data PCGroup = PCGroup { pcs_contains :: HS.HashSet Id, pcs :: HS.HashSet HashedPathCond}
deriving (Show, Eq, Read, Generic, Typeable, Data)
instance Hashable PCGroup
instance Semigroup PCGroup where
pcg1 <> pcg2 =
PCGroup { pcs_contains = pcs_contains pcg1 `HS.union` pcs_contains pcg2
, pcs = pcs pcg1 `HS.union` pcs pcg2 }
instance Monoid PCGroup where
mempty = PCGroup HS.empty HS.empty
mappend = (<>)
mapMaybePCGroup :: (HashedPathCond -> Maybe HashedPathCond) -> PCGroup -> PCGroup
mapMaybePCGroup f pcg =
let
pcs' = HS.map fromJust . HS.filter isJust $ HS.map f (pcs pcg)
in
PCGroup { pcs_contains = HS.fromList (concatMap (varIdsInPC . unhashedPC) pcs'), pcs = pcs' }
unionMapMaybePCGroup :: (HashedPathCond -> HS.HashSet HashedPathCond) -> PCGroup -> PCGroup
unionMapMaybePCGroup f pcg =
let
pcs' = F.foldl' HS.union HS.empty $ HS.map f (pcs pcg)
in
PCGroup { pcs_contains = HS.fromList (concatMap (varIdsInPC . unhashedPC) pcs'), pcs = pcs' }
-- | Path conditions represent logical constraints on our current execution
-- path. We can have path constraints enforced due to case/alt branching, due
-- to assertion / assumptions made, or some externally coded factors.
data PathCond = AltCond Lit Expr Bool -- ^ The expression and Lit must match
| ExtCond Expr Bool -- ^ The expression must be a (true) boolean
| SoftPC PathCond -- ^ A `PathCond` to satisfy if possible, but which is not absolutely required.
| MinimizePC Expr -- ^ An expression to minimize
| AssumePC Id Integer (HS.HashSet HashedPathCond) -- ^ An implication- if the `Id` equals the integer, that implies the `HashedPathCond` in the `HS.HashSet`
deriving (Show, Eq, Read, Generic, Typeable, Data)
type Constraint = PathCond
type Assertion = PathCond
instance Hashable PathCond where
hashWithSalt s pc = s `hashWithSalt` hash pc
hash (AltCond l e b) = (1 :: Int) `hashWithSalt` l `hashWithSalt` e `hashWithSalt` b
hash (ExtCond e b) = (2 :: Int) `hashWithSalt` e `hashWithSalt` b
hash (SoftPC pc) = (3 :: Int) `hashWithSalt` pc
hash (MinimizePC e) = (4 :: Int) `hashWithSalt` e
hash (AssumePC i n pc) = (5 :: Int) `hashWithSalt` i `hashWithSalt` n `hashWithSalt` pc -- hashAssumePC i n pc
{-# INLINE toUFMap #-}
toUFMap :: PathConds -> UF.UFMap (Maybe Name) PCGroup
toUFMap = coerce
fromUFMap :: UF.UFMap (Maybe Name) PCGroup -> PathConds
fromUFMap = coerce
toUFList :: PathConds -> [([Maybe Name], PCGroup)]
toUFList = mapMaybe (\(ns, pc) -> case pc of Just pc' -> Just (ns, pc'); Nothing -> Nothing) . UF.toList . toUFMap
{-# INLINE empty #-}
-- | Constructs an empty `PathConds`.
empty :: PathConds
empty = PathConds UF.empty
fromList :: [PathCond] -> PathConds
fromList = coerce . foldr insert empty
fromHashedList :: [HashedPathCond] -> PathConds
fromHashedList = coerce . foldr insertHashed empty
map :: (PathCond -> PathCond) -> PathConds -> PathConds
map f = fromList . L.map f . toList
mapHashedPCs :: (HashedPathCond -> HashedPathCond) -> PathConds -> PathConds
mapHashedPCs f = fromHashedList . L.map f . toHashedList
map' :: (PathCond -> a) -> PathConds -> [a]
map' f = L.map f . toList
filter :: (PathCond -> Bool) -> PathConds -> PathConds
filter f = fromHashedList
. L.filter (f . unhashedPC)
. toHashedList
alter :: (PathCond -> Maybe PathCond) -> PathConds -> PathConds
alter f = fromList . mapMaybe f . toList
alterHashed :: (HashedPathCond -> Maybe HashedPathCond) -> PathConds -> PathConds
alterHashed f = fromUFMap . UF.map (mapMaybePCGroup f) . toUFMap
unionAlterHashed :: (HashedPathCond -> HS.HashSet HashedPathCond) -> PathConds -> PathConds
unionAlterHashed f = fromUFMap . UF.map (unionMapMaybePCGroup f) . toUFMap
-- Each name n maps to all other names that are in any PathCond containing n
-- However, each n does NOT neccessarily map to all PCs containing n- instead each
-- PC is associated with only one name.
-- This is ok, because the PCs can only be externally accessed by toList (which
-- returns all PCs anyway) or scc (which forces exploration over all shared names)
{-# INLINE insert #-}
insert :: PathCond -> PathConds -> PathConds
insert pc = insertHashed (hashedPC pc)
insertHashed :: HashedPathCond -> PathConds -> PathConds
insertHashed pc (PathConds pcc) =
let
var_ids = varIdsInPC (unhashedPC pc)
sing_pc = PCGroup (HS.fromList var_ids) (HS.singleton pc)
in
case var_ids of
[] -> PathConds $ UF.insertWith (<>) Nothing sing_pc pcc
vs@(v:_) ->
let
ins_pcs = UF.insertWith (<>) (Just (idName v)) sing_pc pcc
in
PathConds $ UF.joinAll (<>) (P.map (Just . idName) vs) ins_pcs
{-# INLINE number #-}
number :: PathConds -> Int
number = length . toList
{-# INLINE null #-}
null :: PathConds -> Bool
null = UF.null . toUFMap
-- Returns a list of PathConds, where the union of the output PathConds
-- is the input PathConds, and the PathCond are seperated into there SCCs
relatedSets :: PathConds -> [PathConds]
relatedSets (PathConds ufm) =
let
c_ufm = UF.clear ufm
in
P.map (\(k, v) -> PathConds $ UF.insert k v c_ufm) $ HM.toList (UF.toSimpleMap ufm)
varIdsInPC :: PathCond -> [Id]
varIdsInPC (AltCond _ e _) = varIds e
varIdsInPC (ExtCond e _) = varIds e
varIdsInPC (MinimizePC e) = varIds e
varIdsInPC (SoftPC pc) = varIdsInPC pc
varIdsInPC (AssumePC i _ pc) = i:concatMap (varIdsInPC . unhashedPC) pc
varNamesInPC :: PathCond -> [Name]
varNamesInPC = P.map idName . varIdsInPC
allIds :: PathConds -> HS.HashSet Id
allIds (PathConds pc) = HS.unions . P.map pcs_contains $ UF.elems pc
-- | Computes the path constraints that relate to the `Names` in the passed list.
scc :: [Name] -> PathConds -> PathConds
scc ns (PathConds pcc) =
let
ns' = P.map (flip UF.find pcc . Just) ns
in
PathConds $ UF.filterWithKey (\k _ -> k `L.elem` ns') pcc
{-# INLINE toList #-}
toList :: PathConds -> [PathCond]
toList = P.map unhashedPC . toHashedList
{-# INLINE toHashedList #-}
toHashedList :: PathConds -> [HashedPathCond]
toHashedList = HS.toList . toHashSet
{-# INLINE toHashSet #-}
toHashSet :: PathConds -> HS.HashSet HashedPathCond
toHashSet = HS.unions . UF.elems . UF.map pcs . toUFMap
union :: PathConds -> PathConds -> PathConds
union (PathConds pc1) (PathConds pc2) = PathConds $ UF.unionWith (<>) pc1 pc2
mergeWithAssumePCs :: Id -> PathConds -> PathConds -> PathConds
mergeWithAssumePCs i (PathConds pc1) (PathConds pc2) =
let
mrg = UF.mergeJoiningWithKey
(mergeMatched i)
(mergeOnlyIn i 1)
(mergeOnlyIn i 2)
(<>)
(<>)
(<>)
pc1 pc2
pc = PathConds $ adjustNothing (idName i) mrg
in
pc
mergeOnlyIn :: Id -> Integer -> Maybe Name -> PCGroup -> (PCGroup, [(Maybe Name, Maybe Name)])
mergeOnlyIn i n k (PCGroup { pcs_contains = contains, pcs = hpc }) =
let
n_hpc = HS.singleton . hashedPC $ mkAssumePC i n hpc -- HS.map (hashedAssumePC i n) hpc
in
( PCGroup { pcs_contains = HS.insert i contains, pcs = n_hpc }
, if not (HS.null hpc) then [(Just $ idName i, k)] else [])
mergeMatched :: Id
-> Maybe Name
-> PCGroup
-> PCGroup
-> (PCGroup, [(Maybe Name, Maybe Name)])
mergeMatched i k (PCGroup { pcs_contains = contains1, pcs = hpc1 }) (PCGroup { pcs_contains = contains2, pcs = hpc2 }) =
let
both = HS.intersection hpc1 hpc2
onlyIn1 = HS.difference hpc1 hpc2 -- HS.map (hashedAssumePC i 1) $ HS.difference hpc1 hpc2
onlyIn2 = HS.difference hpc2 hpc1-- HS.map (hashedAssumePC i 2) $ HS.difference hpc2 hpc1
onlyIn1_pc = if not (HS.null onlyIn1) then HS.singleton . hashedPC $ mkAssumePC i 1 onlyIn1 else HS.empty
onlyIn2_pc = if not (HS.null onlyIn2) then HS.singleton . hashedPC $ mkAssumePC i 2 onlyIn2 else HS.empty
hpc = HS.union both (HS.union onlyIn1_pc onlyIn2_pc)
ks = if not (HS.null onlyIn1) || not (HS.null onlyIn2)
then [(Just $ idName i, k)]
else []
in
( PCGroup { pcs_contains = HS.insert i (contains1 `HS.union` contains2) , pcs = hpc }
, ks)
adjustNothing :: Name
-> UF.UFMap (Maybe Name) PCGroup
-> UF.UFMap (Maybe Name) PCGroup
adjustNothing n hs
| Just v <- UF.lookup Nothing hs = UF.insertWith (<>) (Just n) v hs
| otherwise = hs
instance ASTContainer PathConds Expr where
containedASTs = containedASTs . toUFMap
modifyContainedASTs f = fromList . modifyContainedASTs f . toList
instance ASTContainer PathConds Type where
containedASTs = containedASTs . toUFMap
modifyContainedASTs f = fromList . modifyContainedASTs f . toList
instance ASTContainer PathCond Expr where
containedASTs (ExtCond e _ ) = [e]
containedASTs (AltCond _ e _) = [e]
containedASTs (MinimizePC e) = containedASTs e
containedASTs (SoftPC pc) = containedASTs pc
containedASTs (AssumePC _ _ pc) = containedASTs pc
modifyContainedASTs f (ExtCond e b) = ExtCond (modifyContainedASTs f e) b
modifyContainedASTs f (AltCond a e b) =
AltCond (modifyContainedASTs f a) (modifyContainedASTs f e) b
modifyContainedASTs f (MinimizePC e) = MinimizePC $ modifyContainedASTs f e
modifyContainedASTs f (SoftPC pc) = SoftPC $ modifyContainedASTs f pc
modifyContainedASTs f (AssumePC i num pc) = AssumePC i num (modifyContainedASTs f pc)
instance ASTContainer PathCond Type where
containedASTs (ExtCond e _) = containedASTs e
containedASTs (AltCond e a _) = containedASTs e ++ containedASTs a
containedASTs (MinimizePC pc) = containedASTs pc
containedASTs (SoftPC pc) = containedASTs pc
containedASTs (AssumePC i _ pc) = containedASTs i ++ containedASTs pc
modifyContainedASTs f (ExtCond e b) = ExtCond e' b
where e' = modifyContainedASTs f e
modifyContainedASTs f (AltCond e a b) = AltCond e' a' b
where e' = modifyContainedASTs f e
a' = modifyContainedASTs f a
modifyContainedASTs f (MinimizePC pc) = MinimizePC $ modifyContainedASTs f pc
modifyContainedASTs f (SoftPC pc) = SoftPC $ modifyContainedASTs f pc
modifyContainedASTs f (AssumePC i num pc) = AssumePC (modifyContainedASTs f i) num (modifyContainedASTs f pc)
instance Named PathConds where
-- In rename and renames, we loopup and rename individual keys, to avoid rehashing everything in the PathConds
names = names . UF.toList . toUFMap
rename old new (PathConds pcc) =
let
pcc' = UF.join (<>) (Just old) (Just new) pcc
in
case UF.lookup (Just old) pcc' of
Just pc -> PathConds $ UF.insert (Just new) (rename old new pc) pcc'
Nothing -> PathConds pcc'
renames hm (PathConds pcc) =
let
rep_ns = L.foldr (\k -> HS.insert (UF.find (Just k) pcc)) HS.empty $ HM.keys hm
pcc' = L.foldr (\(k1, k2) -> UF.join (<>) (Just k1) (Just k2)) pcc $ HM.toList hm
in
PathConds $ L.foldr (\k pcs_ ->
case UF.lookup k pcs_ of
Just pc -> UF.insert k (renames hm pc) pcs_
Nothing -> pcs_) pcc' rep_ns
instance ASTContainer PCGroup Expr where
containedASTs = containedASTs . pcs
modifyContainedASTs f pcg = pcg { pcs = modifyContainedASTs f $ pcs pcg }
instance ASTContainer PCGroup Type where
containedASTs = containedASTs . pcs
modifyContainedASTs f pcg = pcg { pcs = modifyContainedASTs f $ pcs pcg }
instance Named PCGroup where
names = names . pcs
rename old new pcg = pcg { pcs_contains = rename old new (pcs_contains pcg)
, pcs = rename old new (pcs pcg) }
renames hm pcg = pcg { pcs_contains = renames hm (pcs_contains pcg)
, pcs = renames hm (pcs pcg) }
instance Ided PCGroup where
ids = ids . pcs
instance Named PathCond where
names (AltCond _ e _) = names e
names (ExtCond e _) = names e
names (MinimizePC pc) = names pc
names (SoftPC pc) = names pc
names (AssumePC i _ pc) = names i <> names pc
rename old new (AltCond l e b) = AltCond l (rename old new e) b
rename old new (ExtCond e b) = ExtCond (rename old new e) b
rename old new (MinimizePC pc) = MinimizePC (rename old new pc)
rename old new (SoftPC pc) = SoftPC (rename old new pc)
rename old new (AssumePC i num pc) = AssumePC (rename old new i) num (rename old new pc)
renames hm (AltCond l e b) = AltCond l (renames hm e) b
renames hm (ExtCond e b) = ExtCond (renames hm e) b
renames hm (MinimizePC pc) = MinimizePC (renames hm pc)
renames hm (SoftPC pc) = SoftPC (renames hm pc)
renames hm (AssumePC i num pc) = AssumePC (renames hm i) num (renames hm pc)
instance Ided PathConds where
ids = ids . toUFMap
instance Ided PathCond where
ids (AltCond _ e _) = ids e
ids (ExtCond e _) = ids e
ids (MinimizePC pc) = ids pc
ids (SoftPC pc) = ids pc
ids (AssumePC i _ pc) = ids i ++ ids pc
data HashedPathCond = HashedPC PathCond {-# UNPACK #-} !Int
deriving (Show, Read, Typeable, Data)
hashedPC :: PathCond -> HashedPathCond
hashedPC pc = HashedPC pc (hash pc)
unhashedPC :: HashedPathCond -> PathCond
unhashedPC (HashedPC pc _) = pc
mapHashedPC :: (PathCond -> PathCond) -> HashedPathCond -> HashedPathCond
mapHashedPC f (HashedPC pc _) = hashedPC (f pc)
instance Eq HashedPathCond where
HashedPC pc h == HashedPC pc' h' = if h /= h' then False else pc == pc'
instance Hashable HashedPathCond where
hashWithSalt s (HashedPC _ h) = s `hashWithSalt` h
hash (HashedPC _ h) = h
instance ASTContainer HashedPathCond Expr where
containedASTs = containedASTs . unhashedPC
modifyContainedASTs f = mapHashedPC (modifyContainedASTs f)
instance ASTContainer HashedPathCond Type where
containedASTs = containedASTs . unhashedPC
modifyContainedASTs f = mapHashedPC (modifyContainedASTs f)
instance Named HashedPathCond where
names = names . unhashedPC
rename old new = mapHashedPC (rename old new)
renames hm = mapHashedPC (renames hm)
instance Ided HashedPathCond where
ids = ids . unhashedPC
mkAssumePC :: Id -> Integer -> HS.HashSet HashedPathCond -> PathCond
mkAssumePC i n = AssumePC i n
mkSingletonAssumePC :: Id -> Integer -> PathCond -> PathCond
mkSingletonAssumePC i n = AssumePC i n . HS.singleton . hashedPC