g2-0.1.0.0: src/G2/Language/PathConds.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module G2.Language.PathConds ( PathCond (..)
, Constraint
, Assertion
, PathConds
, empty
, fromList
, map
, filter
, insert
, null
, number
, relevant
, relatedSets
, scc
, varIdsInPC
, toList
, isPCExists) where
import G2.Language.AST
import G2.Language.Ids
import qualified G2.Language.KnownValues as KV
import G2.Language.Naming
import G2.Language.Syntax
import Data.Coerce
import Data.Data (Data, Typeable)
import GHC.Generics (Generic)
import Data.Hashable
import qualified Data.HashSet as HS
import qualified Data.List as L
import qualified Data.Map as M
import Data.Maybe
import Prelude hiding (map, filter, null)
import qualified Prelude as P (map)
-- In the implementation:
-- Each name (Just n) maps to some (but not neccessarily all) of the PathCond's that
-- contain n, and a list of all names that appear in some PathCond alongside
-- the name n
-- PathConds that contain no names are stored in Nothing
--
-- You can visualize this as a graph, with Names and Nothing as Nodes.
-- Edges exist in a PathConds pcs netween a name n, and any names in
-- snd $ M.lookup n (toMap pcs)
-- | You can visualize a PathConds as [PathCond] (accessible via toList)
newtype PathConds = PathConds (M.Map (Maybe Name) (HS.HashSet PathCond, [Name]))
deriving (Show, Eq, Read, Typeable, Data)
-- | 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
| ConsCond DataCon Expr Bool -- ^ The expression and datacon must match
| PCExists Id -- ^ Makes sure we find some value for the given name, of the correct type
deriving (Show, Eq, Read, Generic, Typeable, Data)
type Constraint = PathCond
type Assertion = PathCond
instance Hashable PathCond
{-# INLINE toMap #-}
toMap :: PathConds -> M.Map (Maybe Name) (HS.HashSet PathCond, [Name])
toMap = coerce
{-# INLINE empty #-}
-- | Constructs an empty `PathConds`.
empty :: PathConds
empty = PathConds M.empty
fromList :: KV.KnownValues -> [PathCond] -> PathConds
fromList kv = coerce . foldr (insert kv) empty
map :: (PathCond -> a) -> PathConds -> [a]
map f = L.map f . toList
filter :: (PathCond -> Bool) -> PathConds -> PathConds
filter f = PathConds
. M.filter (not . HS.null . fst)
. M.map (\(pc, ns) -> (HS.filter f pc, ns))
. toMap
-- 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 :: KV.KnownValues -> PathCond -> PathConds -> PathConds
insert = insert' varNamesInPC
insert' :: (KV.KnownValues -> PathCond -> [Name]) -> KV.KnownValues -> PathCond -> PathConds -> PathConds
insert' f kv p (PathConds pcs) =
let
ns = f kv p
(hd, insertAt) = case ns of
[] -> (Nothing, [Nothing])
(h:_) -> (Just h, P.map Just ns)
in
PathConds $ M.adjust (\(p', ns') -> (HS.insert p p', ns')) hd
$ foldr (M.alter (insert'' ns)) pcs insertAt
insert'' :: [Name] -> Maybe (HS.HashSet PathCond, [Name]) -> Maybe (HS.HashSet PathCond, [Name])
insert'' ns Nothing = Just (HS.empty, ns)
insert'' ns (Just (p', ns')) = Just (p', ns ++ ns')
{-# INLINE number #-}
number :: PathConds -> Int
number = length . toList
{-# INLINE null #-}
null :: PathConds -> Bool
null = M.null . toMap
-- | Filters a PathConds to only those PathCond's that potentially impact the
-- given PathCond's satisfiability (i.e. they are somehow linked by variable names)
relevant :: KV.KnownValues -> [PathCond] -> PathConds -> PathConds
relevant kv pc pcs =
case concatMap (varNamesInPC kv) pc of
[] -> fromList kv pc
rel -> scc rel pcs
-- 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 :: KV.KnownValues -> PathConds -> [PathConds]
relatedSets kv pc@(PathConds pcm) =
let
epc = case M.lookup Nothing pcm of
Just v -> PathConds $ M.singleton Nothing v
Nothing -> PathConds M.empty
ns = catMaybes $ M.keys pcm
in
if null epc then relatedSets' kv pc ns else epc:relatedSets' kv pc ns
relatedSets' :: KV.KnownValues -> PathConds -> [Name] -> [PathConds]
relatedSets' kv pc ns =
case ns of
k:_ ->
let
s = scc [k] pc
ns' = concat $ map (varNamesInPC kv) s
in
s:relatedSets' kv pc (ns L.\\ (k:ns'))
[] -> []
varIdsInPC :: KV.KnownValues -> PathCond -> [Id]
-- [AltCond]
-- Optimization
-- When we have an AltCond with a Var expr, we only have to look at
-- other PC's with that Var's name. This is because we assign all
-- DCs from the same part in a DC tree the same name, and a DC's
-- parents/children can't impose restrictions on it. We are completely
-- guided by pattern matching from case statements.
-- See note [ChildrenNames] in Execution/Rules.hs
varIdsInPC _ (AltCond _ e _) = varIds e
varIdsInPC _ (ExtCond e _) = varIds e
varIdsInPC _ (ConsCond _ e _) = varIds e
varIdsInPC _ (PCExists i) = [i]
varNamesInPC :: KV.KnownValues -> PathCond -> [Name]
varNamesInPC kv = P.map idName . varIdsInPC kv
{-# INLINE scc #-}
scc :: [Name] -> PathConds -> PathConds
scc ns (PathConds pc) = PathConds $ scc' ns pc M.empty
scc' :: [Name]
-> (M.Map (Maybe Name) (HS.HashSet PathCond, [Name]))
-> (M.Map (Maybe Name) (HS.HashSet PathCond, [Name]))
-> (M.Map (Maybe Name) (HS.HashSet PathCond, [Name]))
scc' [] _ pc = pc
scc' (n:ns) pc newpc =
-- Check if we already inserted the name information
case M.lookup (Just n) newpc of
Just _ -> scc' ns pc newpc
Nothing ->
-- If we didn't, lookup info to insert,
-- and add names to the list of names to search
case M.lookup (Just n) pc of
Just pcn@(_, ns') -> scc' (ns ++ ns') pc (M.insert (Just n) pcn newpc)
Nothing -> scc' ns pc newpc
{-# INLINE toList #-}
toList :: PathConds -> [PathCond]
toList = concatMap (HS.toList . fst) . M.elems . toMap
isPCExists :: PathCond -> Bool
isPCExists (PCExists _) = True
isPCExists _ = False
instance ASTContainer PathConds Expr where
containedASTs = containedASTs . toMap
modifyContainedASTs f = coerce . modifyContainedASTs f . toMap
instance ASTContainer PathConds Type where
containedASTs = containedASTs . toMap
modifyContainedASTs f = coerce . modifyContainedASTs f . toMap
instance ASTContainer PathCond Expr where
containedASTs (ExtCond e _ ) = [e]
containedASTs (AltCond _ e _) = [e]
containedASTs (ConsCond _ e _) = [e]
containedASTs (PCExists _) = []
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 (ConsCond dc e b) =
ConsCond (modifyContainedASTs f dc) (modifyContainedASTs f e) b
modifyContainedASTs _ pc = pc
instance ASTContainer PathCond Type where
containedASTs (ExtCond e _) = containedASTs e
containedASTs (AltCond e a _) = containedASTs e ++ containedASTs a
containedASTs (ConsCond dcl e _) = containedASTs dcl ++ containedASTs e
containedASTs (PCExists i) = containedASTs i
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 (ConsCond dc e b) =
ConsCond (modifyContainedASTs f dc) (modifyContainedASTs f e) b
modifyContainedASTs f (PCExists i) = PCExists (modifyContainedASTs f i)
instance Named PathConds where
names (PathConds pc) = (catMaybes $ M.keys pc) ++ concatMap (\(p, n) -> names p ++ n) pc
rename old new (PathConds pc) =
PathConds . M.mapKeys (\k -> if k == (Just old) then (Just new) else k)
$ rename old new pc
renames hm (PathConds pc) =
PathConds . M.mapKeys (renames hm)
$ renames hm pc
instance Named PathCond where
names (AltCond _ e _) = names e
names (ExtCond e _) = names e
names (ConsCond d e _) = names d ++ names e
names (PCExists i) = names i
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 (ConsCond d e b) = ConsCond (rename old new d) (rename old new e) b
rename old new (PCExists i) = PCExists (rename old new i)
renames hm (AltCond l e b) = AltCond l (renames hm e) b
renames hm (ExtCond e b) = ExtCond (renames hm e) b
renames hm (ConsCond d e b) = ConsCond (renames hm d) (renames hm e) b
renames hm (PCExists i) = PCExists (renames hm i)
instance Ided PathConds where
ids = ids . toMap
instance Ided PathCond where
ids (AltCond _ e _) = ids e
ids (ExtCond e _) = ids e
ids (ConsCond d e _) = ids d ++ ids e
ids (PCExists i) = [i]