{-# LANGUAGE NamedFieldPuns #-} module Control.Monad.IOSimPOR.Types ( -- * Effects Effect (..) , readEffects , writeEffects , forkEffect , throwToEffect , wakeupEffects , onlyReadEffect , racingEffects , ppEffect -- * Schedules , ScheduleControl (..) , isDefaultSchedule , ScheduleMod (..) -- * Steps , StepId , ppStepId , Step (..) , StepInfo (..) -- * Races , Races (..) , noRaces ) where import Data.List qualified as List import Data.Set (Set) import Data.Set qualified as Set import Control.Monad.IOSim.CommonTypes -- -- Effects -- -- | An `Effect` aggregates effects performed by a thread in a given -- execution step. Only used by *IOSimPOR*. -- data Effect = Effect { effectReads :: !(Set TVarId), effectWrites :: !(Set TVarId), effectForks :: !(Set IOSimThreadId), effectThrows :: ![IOSimThreadId], effectWakeup :: !(Set IOSimThreadId) } deriving (Show, Eq) ppEffect :: Effect -> String ppEffect Effect { effectReads, effectWrites, effectForks, effectThrows, effectWakeup } = "Effect { " ++ concat (List.intersperse ", " $ [ "reads = " ++ show effectReads | not (null effectReads) ] ++ [ "writes = " ++ show effectWrites | not (null effectWrites) ] ++ [ "forks = " ++ ppList ppIOSimThreadId (Set.toList effectForks) | not (null effectForks) ] ++ [ "throws = " ++ ppList ppIOSimThreadId effectThrows | not (null effectThrows) ] ++ [ "wakeup = " ++ ppList ppIOSimThreadId (Set.toList effectWakeup) | not (null effectWakeup) ]) ++ " }" instance Semigroup Effect where Effect r w s ts wu <> Effect r' w' s' ts' wu' = Effect (r <> r') (w <> w') (s <> s') (ts ++ ts') (wu <> wu') instance Monoid Effect where mempty = Effect Set.empty Set.empty Set.empty [] Set.empty -- -- Effect smart constructors -- -- readEffect :: SomeTVar s -> Effect -- readEffect r = mempty{effectReads = Set.singleton $ someTvarId r } readEffects :: [SomeTVar s] -> Effect readEffects rs = mempty{effectReads = Set.fromList (map someTvarId rs)} -- writeEffect :: SomeTVar s -> Effect -- writeEffect r = mempty{effectWrites = Set.singleton $ someTvarId r } writeEffects :: [SomeTVar s] -> Effect writeEffects rs = mempty{effectWrites = Set.fromList (map someTvarId rs)} forkEffect :: IOSimThreadId -> Effect forkEffect tid = mempty{effectForks = Set.singleton tid} throwToEffect :: IOSimThreadId -> Effect throwToEffect tid = mempty{ effectThrows = [tid] } wakeupEffects :: [IOSimThreadId] -> Effect wakeupEffects tids = mempty{effectWakeup = Set.fromList tids} -- -- Utils -- someTvarId :: SomeTVar s -> TVarId someTvarId (SomeTVar r) = tvarId r onlyReadEffect :: Effect -> Bool onlyReadEffect e = e { effectReads = effectReads mempty } == mempty -- | Check if two effects race. The two effects are assumed to come from -- different threads, from steps which do not wake one another, see -- `racingSteps`. -- racingEffects :: Effect -> Effect -> Bool racingEffects e e' = -- both effects throw to the same threads effectThrows e `intersectsL` effectThrows e' -- concurrent reads & writes of the same TVars || effectReads e `intersects` effectWrites e' || effectWrites e `intersects` effectReads e' -- concurrent writes to the same TVars || effectWrites e `intersects` effectWrites e' where intersects :: Ord a => Set a -> Set a -> Bool intersects a b = not $ a `Set.disjoint` b intersectsL :: Eq a => [a] -> [a] -> Bool intersectsL a b = not $ null $ a `List.intersect` b --- --- Schedules --- -- | Modified execution schedule. -- data ScheduleControl = ControlDefault -- ^ default scheduling mode | ControlAwait [ScheduleMod] -- ^ if the current control is 'ControlAwait', the normal -- scheduling will proceed, until the thread found in the -- first 'ScheduleMod' reaches the given step. At this -- point the thread is put to sleep, until after all the -- steps are followed. | ControlFollow [StepId] [ScheduleMod] -- ^ follow the steps then continue with schedule -- modifications. This control is set by 'followControl' -- when 'controlTargets' returns true. deriving (Eq, Ord, Show) isDefaultSchedule :: ScheduleControl -> Bool isDefaultSchedule ControlDefault = True isDefaultSchedule (ControlFollow [] []) = True isDefaultSchedule _ = False -- | A schedule modification inserted at given execution step. -- data ScheduleMod = ScheduleMod{ -- | Step at which the 'ScheduleMod' is activated. scheduleModTarget :: StepId, -- | 'ScheduleControl' at the activation step. It is needed by -- 'extendScheduleControl' when combining the discovered schedule with the -- initial one. scheduleModControl :: ScheduleControl, -- | Series of steps which are executed at the target step. This *includes* -- the target step, not necessarily as the last step. scheduleModInsertion :: [StepId] } deriving (Eq, Ord) instance Show ScheduleMod where showsPrec d (ScheduleMod tgt ctrl insertion) = showParen (d>10) $ showString "ScheduleMod " . showsPrec 11 tgt . showString " " . showsPrec 11 ctrl . showString " " . showsPrec 11 insertion -- -- Steps -- data Step = Step { stepThreadId :: !IOSimThreadId, stepStep :: !Int, stepEffect :: !Effect, stepVClock :: !VectorClock } deriving Show -- -- StepInfo -- -- | As we execute a simulation, we collect information about each step. This -- information is updated as the simulation evolves by -- `Control.Monad.IOSimPOR.Types.updateRaces`. -- data StepInfo = StepInfo { -- | Step that we want to reschedule to run after a step in `stepInfoRaces` -- (there will be one schedule modification per step in -- `stepInfoRaces`). stepInfoStep :: !Step, -- | Control information when we reached this step. stepInfoControl :: !ScheduleControl, -- | Threads that are still concurrent with this step. stepInfoConcurrent :: !(Set IOSimThreadId), -- | Steps following this one that did not happen after it -- (in reverse order). stepInfoNonDep :: ![Step], -- | Later steps that race with `stepInfoStep`. stepInfoRaces :: ![Step] } deriving Show -- -- Races -- data Races = Races { -- These steps may still race with future steps activeRaces :: ![StepInfo], -- These steps cannot be concurrent with future steps completeRaces :: ![StepInfo] } deriving Show noRaces :: Races noRaces = Races [] []