{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
module Test.DejaFu.Internal where
import Control.DeepSeq (NFData)
import qualified Control.Monad.Conc.Class as C
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe)
import Data.Set (Set)
import qualified Data.Set as S
import GHC.Generics (Generic)
import GHC.Stack (HasCallStack, withFrozenCallStack)
import System.Random (RandomGen)
import Test.DejaFu.Types
data Settings n a = Settings
{ _way :: Way
, _memtype :: MemType
, _discard :: Maybe (Either Failure a -> Maybe Discard)
, _debugShow :: Maybe (a -> String)
, _debugPrint :: Maybe (String -> n ())
, _debugFatal :: Bool
, _earlyExit :: Maybe (Either Failure a -> Bool)
, _equality :: Maybe (a -> a -> Bool)
, _simplify :: Bool
}
data Way where
Systematic :: Bounds -> Way
Randomly :: RandomGen g => (g -> (Int, g)) -> g -> Int -> Way
instance Show Way where
show (Systematic bs) = "Systematic (" ++ show bs ++ ")"
show (Randomly _ _ n) = "Randomly <f> <gen> " ++ show n
data IdSource = IdSource
{ _crids :: (Int, [String])
, _mvids :: (Int, [String])
, _tvids :: (Int, [String])
, _tids :: (Int, [String])
} deriving (Eq, Ord, Show, Generic, NFData)
nextCRId :: String -> IdSource -> (IdSource, CRefId)
nextCRId name idsource =
let (crid, crids') = nextId name (_crids idsource)
in (idsource { _crids = crids' }, CRefId crid)
nextMVId :: String -> IdSource -> (IdSource, MVarId)
nextMVId name idsource =
let (mvid, mvids') = nextId name (_mvids idsource)
in (idsource { _mvids = mvids' }, MVarId mvid)
nextTVId :: String -> IdSource -> (IdSource, TVarId)
nextTVId name idsource =
let (tvid, tvids') = nextId name (_tvids idsource)
in (idsource { _tvids = tvids' }, TVarId tvid)
nextTId :: String -> IdSource -> (IdSource, ThreadId)
nextTId name idsource =
let (tid, tids') = nextId name (_tids idsource)
in (idsource { _tids = tids' }, ThreadId tid)
nextId :: String -> (Int, [String]) -> (Id, (Int, [String]))
nextId name (num, used) = (Id newName (num+1), (num+1, newUsed)) where
newName
| null name = Nothing
| occurrences > 0 = Just (name ++ "-" ++ show occurrences)
| otherwise = Just name
newUsed
| null name = used
| otherwise = name : used
occurrences = length (filter (==name) used)
initialIdSource :: IdSource
initialIdSource = IdSource (0, []) (0, []) (0, []) (0, [])
isBlock :: ThreadAction -> Bool
isBlock (BlockedThrowTo _) = True
isBlock (BlockedTakeMVar _) = True
isBlock (BlockedReadMVar _) = True
isBlock (BlockedPutMVar _) = True
isBlock (BlockedSTM _) = True
isBlock _ = False
tvarsOf :: ThreadAction -> Set TVarId
tvarsOf act = tvarsRead act `S.union` tvarsWritten act
tvarsWritten :: ThreadAction -> Set TVarId
tvarsWritten act = S.fromList $ case act of
STM trc _ -> concatMap tvarsOf' trc
BlockedSTM trc -> concatMap tvarsOf' trc
_ -> []
where
tvarsOf' (TNew tv) = [tv]
tvarsOf' (TWrite tv) = [tv]
tvarsOf' (TOrElse ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)
tvarsOf' (TCatch ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)
tvarsOf' _ = []
tvarsRead :: ThreadAction -> Set TVarId
tvarsRead act = S.fromList $ case act of
STM trc _ -> concatMap tvarsOf' trc
BlockedSTM trc -> concatMap tvarsOf' trc
_ -> []
where
tvarsOf' (TRead tv) = [tv]
tvarsOf' (TOrElse ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)
tvarsOf' (TCatch ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)
tvarsOf' _ = []
rewind :: ThreadAction -> Lookahead
rewind (Fork _) = WillFork
rewind (ForkOS _) = WillForkOS
rewind (IsCurrentThreadBound _) = WillIsCurrentThreadBound
rewind MyThreadId = WillMyThreadId
rewind (GetNumCapabilities _) = WillGetNumCapabilities
rewind (SetNumCapabilities i) = WillSetNumCapabilities i
rewind Yield = WillYield
rewind (ThreadDelay n) = WillThreadDelay n
rewind (NewMVar _) = WillNewMVar
rewind (PutMVar c _) = WillPutMVar c
rewind (BlockedPutMVar c) = WillPutMVar c
rewind (TryPutMVar c _ _) = WillTryPutMVar c
rewind (ReadMVar c) = WillReadMVar c
rewind (BlockedReadMVar c) = WillReadMVar c
rewind (TryReadMVar c _) = WillTryReadMVar c
rewind (TakeMVar c _) = WillTakeMVar c
rewind (BlockedTakeMVar c) = WillTakeMVar c
rewind (TryTakeMVar c _ _) = WillTryTakeMVar c
rewind (NewCRef _) = WillNewCRef
rewind (ReadCRef c) = WillReadCRef c
rewind (ReadCRefCas c) = WillReadCRefCas c
rewind (ModCRef c) = WillModCRef c
rewind (ModCRefCas c) = WillModCRefCas c
rewind (WriteCRef c) = WillWriteCRef c
rewind (CasCRef c _) = WillCasCRef c
rewind (CommitCRef t c) = WillCommitCRef t c
rewind (STM _ _) = WillSTM
rewind (BlockedSTM _) = WillSTM
rewind Catching = WillCatching
rewind PopCatching = WillPopCatching
rewind Throw = WillThrow
rewind (ThrowTo t) = WillThrowTo t
rewind (BlockedThrowTo t) = WillThrowTo t
rewind (SetMasking b m) = WillSetMasking b m
rewind (ResetMasking b m) = WillResetMasking b m
rewind LiftIO = WillLiftIO
rewind Return = WillReturn
rewind Stop = WillStop
rewind Subconcurrency = WillSubconcurrency
rewind StopSubconcurrency = WillStopSubconcurrency
rewind (DontCheck _) = WillDontCheck
willRelease :: Lookahead -> Bool
willRelease WillFork = True
willRelease WillForkOS = True
willRelease WillYield = True
willRelease (WillThreadDelay _) = True
willRelease (WillPutMVar _) = True
willRelease (WillTryPutMVar _) = True
willRelease (WillReadMVar _) = True
willRelease (WillTakeMVar _) = True
willRelease (WillTryTakeMVar _) = True
willRelease WillSTM = True
willRelease WillThrow = True
willRelease (WillSetMasking _ _) = True
willRelease (WillResetMasking _ _) = True
willRelease WillStop = True
willRelease WillDontCheck = True
willRelease _ = False
data ActionType =
UnsynchronisedRead CRefId
| UnsynchronisedWrite CRefId
| UnsynchronisedOther
| PartiallySynchronisedCommit CRefId
| PartiallySynchronisedWrite CRefId
| PartiallySynchronisedModify CRefId
| SynchronisedModify CRefId
| SynchronisedRead MVarId
| SynchronisedWrite MVarId
| SynchronisedOther
deriving (Eq, Show, Generic, NFData)
isBarrier :: ActionType -> Bool
isBarrier (SynchronisedModify _) = True
isBarrier (SynchronisedRead _) = True
isBarrier (SynchronisedWrite _) = True
isBarrier SynchronisedOther = True
isBarrier _ = False
isCommit :: ActionType -> CRefId -> Bool
isCommit (PartiallySynchronisedCommit c) r = c == r
isCommit (PartiallySynchronisedWrite c) r = c == r
isCommit (PartiallySynchronisedModify c) r = c == r
isCommit _ _ = False
synchronises :: ActionType -> CRefId -> Bool
synchronises a r = isCommit a r || isBarrier a
crefOf :: ActionType -> Maybe CRefId
crefOf (UnsynchronisedRead r) = Just r
crefOf (UnsynchronisedWrite r) = Just r
crefOf (SynchronisedModify r) = Just r
crefOf (PartiallySynchronisedCommit r) = Just r
crefOf (PartiallySynchronisedWrite r) = Just r
crefOf (PartiallySynchronisedModify r) = Just r
crefOf _ = Nothing
mvarOf :: ActionType -> Maybe MVarId
mvarOf (SynchronisedRead c) = Just c
mvarOf (SynchronisedWrite c) = Just c
mvarOf _ = Nothing
tidsOf :: ThreadAction -> Set ThreadId
tidsOf (Fork tid) = S.singleton tid
tidsOf (ForkOS tid) = S.singleton tid
tidsOf (PutMVar _ tids) = S.fromList tids
tidsOf (TryPutMVar _ _ tids) = S.fromList tids
tidsOf (TakeMVar _ tids) = S.fromList tids
tidsOf (TryTakeMVar _ _ tids) = S.fromList tids
tidsOf (CommitCRef tid _) = S.singleton tid
tidsOf (STM _ tids) = S.fromList tids
tidsOf (ThrowTo tid) = S.singleton tid
tidsOf (BlockedThrowTo tid) = S.singleton tid
tidsOf _ = S.empty
simplifyAction :: ThreadAction -> ActionType
simplifyAction = simplifyLookahead . rewind
simplifyLookahead :: Lookahead -> ActionType
simplifyLookahead (WillPutMVar c) = SynchronisedWrite c
simplifyLookahead (WillTryPutMVar c) = SynchronisedWrite c
simplifyLookahead (WillReadMVar c) = SynchronisedRead c
simplifyLookahead (WillTryReadMVar c) = SynchronisedRead c
simplifyLookahead (WillTakeMVar c) = SynchronisedRead c
simplifyLookahead (WillTryTakeMVar c) = SynchronisedRead c
simplifyLookahead (WillReadCRef r) = UnsynchronisedRead r
simplifyLookahead (WillReadCRefCas r) = UnsynchronisedRead r
simplifyLookahead (WillModCRef r) = SynchronisedModify r
simplifyLookahead (WillModCRefCas r) = PartiallySynchronisedModify r
simplifyLookahead (WillWriteCRef r) = UnsynchronisedWrite r
simplifyLookahead (WillCasCRef r) = PartiallySynchronisedWrite r
simplifyLookahead (WillCommitCRef _ r) = PartiallySynchronisedCommit r
simplifyLookahead WillSTM = SynchronisedOther
simplifyLookahead (WillThrowTo _) = SynchronisedOther
simplifyLookahead _ = UnsynchronisedOther
etail :: HasCallStack => [a] -> [a]
etail (_:xs) = xs
etail _ = withFrozenCallStack $ fatal "tail: empty list"
eidx :: HasCallStack => [a] -> Int -> a
eidx xs i
| i < length xs = xs !! i
| otherwise = withFrozenCallStack $ fatal "(!!): index too large"
efromJust :: HasCallStack => Maybe a -> a
efromJust (Just x) = x
efromJust _ = withFrozenCallStack $ fatal "fromJust: Nothing"
efromList :: HasCallStack => [a] -> NonEmpty a
efromList (x:xs) = x:|xs
efromList _ = withFrozenCallStack $ fatal "fromList: empty list"
eadjust :: (Ord k, Show k, HasCallStack) => (v -> v) -> k -> M.Map k v -> M.Map k v
eadjust f k m = case M.lookup k m of
Just v -> M.insert k (f v) m
Nothing -> withFrozenCallStack $ fatal ("adjust: key '" ++ show k ++ "' not found")
einsert :: (Ord k, Show k, HasCallStack) => k -> v -> M.Map k v -> M.Map k v
einsert k v m
| M.member k m = withFrozenCallStack $ fatal ("insert: key '" ++ show k ++ "' already present")
| otherwise = M.insert k v m
elookup :: (Ord k, Show k, HasCallStack) => k -> M.Map k v -> v
elookup k =
fromMaybe (withFrozenCallStack $ fatal ("lookup: key '" ++ show k ++ "' not found")) .
M.lookup k
fatal :: HasCallStack => String -> a
fatal msg = withFrozenCallStack $ error ("(dejafu) " ++ msg)
runRefCont :: C.MonadConc n
=> (n () -> x)
-> (a -> Maybe b)
-> ((a -> x) -> x)
-> n (x, C.CRef n (Maybe b))
runRefCont act f k = do
ref <- C.newCRef Nothing
let c = k (act . C.writeCRef ref . f)
pure (c, ref)